333 lines
8.2 KiB
C
333 lines
8.2 KiB
C
/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
/* Qualcomm Technologies, Inc. EMAC SGMII Controller driver.
|
|
*/
|
|
|
|
#include "emac_sgmii.h"
|
|
#include "emac_hw.h"
|
|
|
|
int emac_sgmii_config(struct platform_device *pdev, struct emac_adapter *adpt)
|
|
{
|
|
struct emac_sgmii *sgmii;
|
|
struct resource *res;
|
|
int ret;
|
|
|
|
sgmii = devm_kzalloc(&pdev->dev, sizeof(*sgmii), GFP_KERNEL);
|
|
if (!sgmii)
|
|
return -ENOMEM;
|
|
|
|
ret = platform_get_irq_byname(pdev, "emac_sgmii_irq");
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
sgmii->irq = ret;
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "emac_sgmii");
|
|
if (!res) {
|
|
emac_err(adpt,
|
|
"error platform_get_resource_byname(emac_sgmii)\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
sgmii->base = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(sgmii->base)) {
|
|
emac_err(adpt,
|
|
"error:%ld devm_ioremap_resource(start:0x%lx size:0x%lx)\n",
|
|
PTR_ERR(sgmii->base), (ulong)res->start,
|
|
(ulong)resource_size(res));
|
|
return -ENOMEM;
|
|
}
|
|
|
|
adpt->phy.private = sgmii;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void emac_sgmii_reset_prepare(struct emac_adapter *adpt)
|
|
{
|
|
struct emac_sgmii *sgmii = adpt->phy.private;
|
|
u32 val;
|
|
|
|
val = readl_relaxed(sgmii->base + EMAC_EMAC_WRAPPER_CSR2);
|
|
writel_relaxed(((val & ~PHY_RESET) | PHY_RESET),
|
|
sgmii->base + EMAC_EMAC_WRAPPER_CSR2);
|
|
/* Ensure phy-reset command is written to HW before the release cmd */
|
|
wmb();
|
|
msleep(50);
|
|
val = readl_relaxed(sgmii->base + EMAC_EMAC_WRAPPER_CSR2);
|
|
writel_relaxed((val & ~PHY_RESET),
|
|
sgmii->base + EMAC_EMAC_WRAPPER_CSR2);
|
|
/* Ensure phy-reset release command is written to HW before initializing
|
|
* SGMII
|
|
*/
|
|
wmb();
|
|
msleep(50);
|
|
}
|
|
|
|
/* LINK */
|
|
int emac_sgmii_init_link(struct emac_adapter *adpt, u32 speed, bool autoneg,
|
|
bool fc)
|
|
{
|
|
struct emac_sgmii *sgmii = adpt->phy.private;
|
|
u32 val;
|
|
u32 speed_cfg = 0;
|
|
|
|
val = readl_relaxed(sgmii->base + EMAC_SGMII_PHY_AUTONEG_CFG2);
|
|
|
|
if (autoneg) {
|
|
val &= ~(FORCE_AN_RX_CFG | FORCE_AN_TX_CFG);
|
|
val |= AN_ENABLE;
|
|
writel_relaxed(val, sgmii->base + EMAC_SGMII_PHY_AUTONEG_CFG2);
|
|
} else {
|
|
switch (speed) {
|
|
case EMAC_LINK_SPEED_10_HALF:
|
|
speed_cfg = SPDMODE_10;
|
|
break;
|
|
case EMAC_LINK_SPEED_10_FULL:
|
|
speed_cfg = SPDMODE_10 | DUPLEX_MODE;
|
|
break;
|
|
case EMAC_LINK_SPEED_100_HALF:
|
|
speed_cfg = SPDMODE_100;
|
|
break;
|
|
case EMAC_LINK_SPEED_100_FULL:
|
|
speed_cfg = SPDMODE_100 | DUPLEX_MODE;
|
|
break;
|
|
case EMAC_LINK_SPEED_1GB_FULL:
|
|
speed_cfg = SPDMODE_1000 | DUPLEX_MODE;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
val &= ~AN_ENABLE;
|
|
writel_relaxed(speed_cfg,
|
|
sgmii->base + EMAC_SGMII_PHY_SPEED_CFG1);
|
|
writel_relaxed(val, sgmii->base + EMAC_SGMII_PHY_AUTONEG_CFG2);
|
|
}
|
|
/* Ensure Auto-Neg setting are written to HW before leaving */
|
|
wmb();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int emac_hw_clear_sgmii_intr_status(struct emac_adapter *adpt, u32 irq_bits)
|
|
{
|
|
struct emac_sgmii *sgmii = adpt->phy.private;
|
|
u32 status;
|
|
int i;
|
|
|
|
writel_relaxed(irq_bits, sgmii->base + EMAC_SGMII_PHY_INTERRUPT_CLEAR);
|
|
writel_relaxed(IRQ_GLOBAL_CLEAR, sgmii->base + EMAC_SGMII_PHY_IRQ_CMD);
|
|
/* Ensure interrupt clear command is written to HW */
|
|
wmb();
|
|
|
|
/* After set the IRQ_GLOBAL_CLEAR bit, the status clearing must
|
|
* be confirmed before clearing the bits in other registers.
|
|
* It takes a few cycles for hw to clear the interrupt status.
|
|
*/
|
|
for (i = 0; i < SGMII_PHY_IRQ_CLR_WAIT_TIME; i++) {
|
|
udelay(1);
|
|
status = readl_relaxed(sgmii->base +
|
|
EMAC_SGMII_PHY_INTERRUPT_STATUS);
|
|
if (!(status & irq_bits))
|
|
break;
|
|
}
|
|
if (status & irq_bits) {
|
|
emac_err(adpt,
|
|
"failed to clear SGMII irq: status 0x%x bits 0x%x\n",
|
|
status, irq_bits);
|
|
return -EIO;
|
|
}
|
|
|
|
/* Finalize clearing procedure */
|
|
writel_relaxed(0, sgmii->base + EMAC_SGMII_PHY_IRQ_CMD);
|
|
writel_relaxed(0, sgmii->base + EMAC_SGMII_PHY_INTERRUPT_CLEAR);
|
|
/* Ensure that clearing procedure finalization is written to HW */
|
|
wmb();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int emac_sgmii_init_ephy_nop(struct emac_adapter *adpt)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int emac_sgmii_autoneg_check(struct emac_adapter *adpt, u32 *speed,
|
|
bool *link_up)
|
|
{
|
|
struct emac_sgmii *sgmii = adpt->phy.private;
|
|
u32 autoneg0, autoneg1, status;
|
|
|
|
autoneg0 = readl_relaxed(sgmii->base + EMAC_SGMII_PHY_AUTONEG0_STATUS);
|
|
autoneg1 = readl_relaxed(sgmii->base + EMAC_SGMII_PHY_AUTONEG1_STATUS);
|
|
status = ((autoneg1 & 0xff) << 8) | (autoneg0 & 0xff);
|
|
|
|
if (!(status & TXCFG_LINK)) {
|
|
*link_up = false;
|
|
*speed = EMAC_LINK_SPEED_UNKNOWN;
|
|
return 0;
|
|
}
|
|
|
|
*link_up = true;
|
|
|
|
switch (status & TXCFG_MODE_BMSK) {
|
|
case TXCFG_1000_FULL:
|
|
*speed = EMAC_LINK_SPEED_1GB_FULL;
|
|
break;
|
|
case TXCFG_100_FULL:
|
|
*speed = EMAC_LINK_SPEED_100_FULL;
|
|
break;
|
|
case TXCFG_100_HALF:
|
|
*speed = EMAC_LINK_SPEED_100_HALF;
|
|
break;
|
|
case TXCFG_10_FULL:
|
|
*speed = EMAC_LINK_SPEED_10_FULL;
|
|
break;
|
|
case TXCFG_10_HALF:
|
|
*speed = EMAC_LINK_SPEED_10_HALF;
|
|
break;
|
|
default:
|
|
*speed = EMAC_LINK_SPEED_UNKNOWN;
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int emac_sgmii_link_check_no_ephy(struct emac_adapter *adpt, u32 *speed,
|
|
bool *link_up)
|
|
{
|
|
struct emac_sgmii *sgmii = adpt->phy.private;
|
|
u32 val;
|
|
|
|
val = readl_relaxed(sgmii->base + EMAC_SGMII_PHY_AUTONEG_CFG2);
|
|
if (val & AN_ENABLE)
|
|
return emac_sgmii_autoneg_check(adpt, speed, link_up);
|
|
|
|
val = readl_relaxed(sgmii->base + EMAC_SGMII_PHY_SPEED_CFG1);
|
|
val &= DUPLEX_MODE | SPDMODE_BMSK;
|
|
switch (val) {
|
|
case DUPLEX_MODE | SPDMODE_1000:
|
|
*speed = EMAC_LINK_SPEED_1GB_FULL;
|
|
break;
|
|
case DUPLEX_MODE | SPDMODE_100:
|
|
*speed = EMAC_LINK_SPEED_100_FULL;
|
|
break;
|
|
case SPDMODE_100:
|
|
*speed = EMAC_LINK_SPEED_100_HALF;
|
|
break;
|
|
case DUPLEX_MODE | SPDMODE_10:
|
|
*speed = EMAC_LINK_SPEED_10_FULL;
|
|
break;
|
|
case SPDMODE_10:
|
|
*speed = EMAC_LINK_SPEED_10_HALF;
|
|
break;
|
|
default:
|
|
*speed = EMAC_LINK_SPEED_UNKNOWN;
|
|
break;
|
|
}
|
|
*link_up = true;
|
|
return 0;
|
|
}
|
|
|
|
irqreturn_t emac_sgmii_isr(int _irq, void *data)
|
|
{
|
|
struct emac_adapter *adpt = data;
|
|
struct emac_sgmii *sgmii = adpt->phy.private;
|
|
u32 status;
|
|
|
|
emac_dbg(adpt, intr, "receive sgmii interrupt\n");
|
|
|
|
do {
|
|
status = readl_relaxed(sgmii->base +
|
|
EMAC_SGMII_PHY_INTERRUPT_STATUS) &
|
|
SGMII_ISR_MASK;
|
|
if (!status)
|
|
break;
|
|
|
|
if (status & SGMII_PHY_INTERRUPT_ERR) {
|
|
SET_FLAG(adpt, ADPT_TASK_CHK_SGMII_REQ);
|
|
if (!TEST_FLAG(adpt, ADPT_STATE_DOWN))
|
|
emac_task_schedule(adpt);
|
|
}
|
|
|
|
if (status & SGMII_ISR_AN_MASK)
|
|
emac_check_lsc(adpt);
|
|
|
|
if (emac_hw_clear_sgmii_intr_status(adpt, status) != 0) {
|
|
/* reset */
|
|
SET_FLAG(adpt, ADPT_TASK_REINIT_REQ);
|
|
emac_task_schedule(adpt);
|
|
break;
|
|
}
|
|
} while (1);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
int emac_sgmii_up(struct emac_adapter *adpt)
|
|
{
|
|
struct emac_sgmii *sgmii = adpt->phy.private;
|
|
int ret;
|
|
|
|
ret = request_irq(sgmii->irq, emac_sgmii_isr, IRQF_TRIGGER_RISING,
|
|
"sgmii_irq", adpt);
|
|
if (ret)
|
|
emac_err(adpt,
|
|
"error:%d on request_irq(%d:sgmii_irq)\n", ret,
|
|
sgmii->irq);
|
|
|
|
/* enable sgmii irq */
|
|
writel_relaxed(SGMII_ISR_MASK,
|
|
sgmii->base + EMAC_SGMII_PHY_INTERRUPT_MASK);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void emac_sgmii_down(struct emac_adapter *adpt)
|
|
{
|
|
struct emac_sgmii *sgmii = adpt->phy.private;
|
|
|
|
writel_relaxed(0, sgmii->base + EMAC_SGMII_PHY_INTERRUPT_MASK);
|
|
synchronize_irq(sgmii->irq);
|
|
free_irq(sgmii->irq, adpt);
|
|
}
|
|
|
|
void emac_sgmii_tx_clk_set_rate_nop(struct emac_adapter *adpt)
|
|
{
|
|
}
|
|
|
|
/* Check SGMII for error */
|
|
void emac_sgmii_periodic_check(struct emac_adapter *adpt)
|
|
{
|
|
struct emac_sgmii *sgmii = adpt->phy.private;
|
|
|
|
if (!TEST_FLAG(adpt, ADPT_TASK_CHK_SGMII_REQ))
|
|
return;
|
|
CLR_FLAG(adpt, ADPT_TASK_CHK_SGMII_REQ);
|
|
|
|
/* ensure that no reset is in progress while link task is running */
|
|
while (TEST_N_SET_FLAG(adpt, ADPT_STATE_RESETTING))
|
|
msleep(20); /* Reset might take few 10s of ms */
|
|
|
|
if (TEST_FLAG(adpt, ADPT_STATE_DOWN))
|
|
goto sgmii_task_done;
|
|
|
|
if (readl_relaxed(sgmii->base + EMAC_SGMII_PHY_RX_CHK_STATUS) & 0x40)
|
|
goto sgmii_task_done;
|
|
|
|
emac_err(adpt, "SGMII CDR not locked\n");
|
|
|
|
sgmii_task_done:
|
|
CLR_FLAG(adpt, ADPT_STATE_RESETTING);
|
|
}
|