diff options
Diffstat (limited to 'drivers/net/ethernet/qualcomm/emac/emac-sgmii.c')
-rw-r--r-- | drivers/net/ethernet/qualcomm/emac/emac-sgmii.c | 183 |
1 files changed, 137 insertions, 46 deletions
diff --git a/drivers/net/ethernet/qualcomm/emac/emac-sgmii.c b/drivers/net/ethernet/qualcomm/emac/emac-sgmii.c index bf722a9bb09d..040b28977ee7 100644 --- a/drivers/net/ethernet/qualcomm/emac/emac-sgmii.c +++ b/drivers/net/ethernet/qualcomm/emac/emac-sgmii.c @@ -25,7 +25,9 @@ #define EMAC_SGMII_PHY_SPEED_CFG1 0x0074 #define EMAC_SGMII_PHY_IRQ_CMD 0x00ac #define EMAC_SGMII_PHY_INTERRUPT_CLEAR 0x00b0 +#define EMAC_SGMII_PHY_INTERRUPT_MASK 0x00b4 #define EMAC_SGMII_PHY_INTERRUPT_STATUS 0x00b8 +#define EMAC_SGMII_PHY_RX_CHK_STATUS 0x00d4 #define FORCE_AN_TX_CFG BIT(5) #define FORCE_AN_RX_CFG BIT(4) @@ -36,6 +38,8 @@ #define SPDMODE_100 BIT(0) #define SPDMODE_10 0 +#define CDR_ALIGN_DET BIT(6) + #define IRQ_GLOBAL_CLEAR BIT(0) #define DECODE_CODE_ERR BIT(7) @@ -44,52 +48,28 @@ #define SGMII_PHY_IRQ_CLR_WAIT_TIME 10 #define SGMII_PHY_INTERRUPT_ERR (DECODE_CODE_ERR | DECODE_DISP_ERR) +#define SGMII_ISR_MASK (SGMII_PHY_INTERRUPT_ERR) #define SERDES_START_WAIT_TIMES 100 -static int emac_sgmii_link_init(struct emac_adapter *adpt) +/* Initialize the SGMII link between the internal and external PHYs. */ +static void emac_sgmii_link_init(struct emac_adapter *adpt) { - struct phy_device *phydev = adpt->phydev; - struct emac_phy *phy = &adpt->phy; + struct emac_sgmii *phy = &adpt->phy; u32 val; + /* Always use autonegotiation. It works no matter how the external + * PHY is configured. + */ val = readl(phy->base + EMAC_SGMII_PHY_AUTONEG_CFG2); - - if (phydev->autoneg == AUTONEG_ENABLE) { - val &= ~(FORCE_AN_RX_CFG | FORCE_AN_TX_CFG); - val |= AN_ENABLE; - writel(val, phy->base + EMAC_SGMII_PHY_AUTONEG_CFG2); - } else { - u32 speed_cfg; - - switch (phydev->speed) { - case SPEED_10: - speed_cfg = SPDMODE_10; - break; - case SPEED_100: - speed_cfg = SPDMODE_100; - break; - case SPEED_1000: - speed_cfg = SPDMODE_1000; - break; - default: - return -EINVAL; - } - - if (phydev->duplex == DUPLEX_FULL) - speed_cfg |= DUPLEX_MODE; - - val &= ~AN_ENABLE; - writel(speed_cfg, phy->base + EMAC_SGMII_PHY_SPEED_CFG1); - writel(val, phy->base + EMAC_SGMII_PHY_AUTONEG_CFG2); - } - - return 0; + val &= ~(FORCE_AN_RX_CFG | FORCE_AN_TX_CFG); + val |= AN_ENABLE; + writel(val, phy->base + EMAC_SGMII_PHY_AUTONEG_CFG2); } static int emac_sgmii_irq_clear(struct emac_adapter *adpt, u32 irq_bits) { - struct emac_phy *phy = &adpt->phy; + struct emac_sgmii *phy = &adpt->phy; u32 status; writel_relaxed(irq_bits, phy->base + EMAC_SGMII_PHY_INTERRUPT_CLEAR); @@ -121,9 +101,54 @@ static int emac_sgmii_irq_clear(struct emac_adapter *adpt, u32 irq_bits) return 0; } +/* The number of decode errors that triggers a reset */ +#define DECODE_ERROR_LIMIT 2 + +static irqreturn_t emac_sgmii_interrupt(int irq, void *data) +{ + struct emac_adapter *adpt = data; + struct emac_sgmii *phy = &adpt->phy; + u32 status; + + status = readl(phy->base + EMAC_SGMII_PHY_INTERRUPT_STATUS); + status &= SGMII_ISR_MASK; + if (!status) + return IRQ_HANDLED; + + /* If we get a decoding error and CDR is not locked, then try + * resetting the internal PHY. The internal PHY uses an embedded + * clock with Clock and Data Recovery (CDR) to recover the + * clock and data. + */ + if (status & SGMII_PHY_INTERRUPT_ERR) { + int count; + + /* The SGMII is capable of recovering from some decode + * errors automatically. However, if we get multiple + * decode errors in a row, then assume that something + * is wrong and reset the interface. + */ + count = atomic_inc_return(&phy->decode_error_count); + if (count == DECODE_ERROR_LIMIT) { + schedule_work(&adpt->work_thread); + atomic_set(&phy->decode_error_count, 0); + } + } else { + /* We only care about consecutive decode errors. */ + atomic_set(&phy->decode_error_count, 0); + } + + if (emac_sgmii_irq_clear(adpt, status)) { + netdev_warn(adpt->netdev, "failed to clear SGMII interrupt\n"); + schedule_work(&adpt->work_thread); + } + + return IRQ_HANDLED; +} + static void emac_sgmii_reset_prepare(struct emac_adapter *adpt) { - struct emac_phy *phy = &adpt->phy; + struct emac_sgmii *phy = &adpt->phy; u32 val; /* Reset PHY */ @@ -145,12 +170,7 @@ void emac_sgmii_reset(struct emac_adapter *adpt) int ret; emac_sgmii_reset_prepare(adpt); - - ret = emac_sgmii_link_init(adpt); - if (ret) { - netdev_err(adpt->netdev, "unsupported link speed\n"); - return; - } + emac_sgmii_link_init(adpt); ret = adpt->phy.initialize(adpt); if (ret) @@ -159,6 +179,68 @@ void emac_sgmii_reset(struct emac_adapter *adpt) ret); } +static int emac_sgmii_open(struct emac_adapter *adpt) +{ + struct emac_sgmii *sgmii = &adpt->phy; + int ret; + + if (sgmii->irq) { + /* Make sure interrupts are cleared and disabled first */ + ret = emac_sgmii_irq_clear(adpt, 0xff); + if (ret) + return ret; + writel(0, sgmii->base + EMAC_SGMII_PHY_INTERRUPT_MASK); + + ret = request_irq(sgmii->irq, emac_sgmii_interrupt, 0, + "emac-sgmii", adpt); + if (ret) { + netdev_err(adpt->netdev, + "could not register handler for internal PHY\n"); + return ret; + } + } + + return 0; +} + +static int emac_sgmii_close(struct emac_adapter *adpt) +{ + struct emac_sgmii *sgmii = &adpt->phy; + + /* Make sure interrupts are disabled */ + writel(0, sgmii->base + EMAC_SGMII_PHY_INTERRUPT_MASK); + free_irq(sgmii->irq, adpt); + + return 0; +} + +/* The error interrupts are only valid after the link is up */ +static int emac_sgmii_link_up(struct emac_adapter *adpt) +{ + struct emac_sgmii *sgmii = &adpt->phy; + int ret; + + /* Clear and enable interrupts */ + ret = emac_sgmii_irq_clear(adpt, 0xff); + if (ret) + return ret; + + writel(SGMII_ISR_MASK, sgmii->base + EMAC_SGMII_PHY_INTERRUPT_MASK); + + return 0; +} + +static int emac_sgmii_link_down(struct emac_adapter *adpt) +{ + struct emac_sgmii *sgmii = &adpt->phy; + + /* Disable interrupts */ + writel(0, sgmii->base + EMAC_SGMII_PHY_INTERRUPT_MASK); + synchronize_irq(sgmii->irq); + + return 0; +} + static int emac_sgmii_acpi_match(struct device *dev, void *data) { #ifdef CONFIG_ACPI @@ -169,7 +251,7 @@ static int emac_sgmii_acpi_match(struct device *dev, void *data) {} }; const struct acpi_device_id *id = acpi_match_device(match_table, dev); - emac_sgmii_initialize *initialize = data; + emac_sgmii_function *initialize = data; if (id) { acpi_handle handle = ACPI_HANDLE(dev); @@ -217,7 +299,7 @@ static const struct of_device_id emac_sgmii_dt_match[] = { int emac_sgmii_config(struct platform_device *pdev, struct emac_adapter *adpt) { struct platform_device *sgmii_pdev = NULL; - struct emac_phy *phy = &adpt->phy; + struct emac_sgmii *phy = &adpt->phy; struct resource *res; int ret; @@ -256,9 +338,14 @@ int emac_sgmii_config(struct platform_device *pdev, struct emac_adapter *adpt) goto error_put_device; } - phy->initialize = (emac_sgmii_initialize)match->data; + phy->initialize = (emac_sgmii_function)match->data; } + phy->open = emac_sgmii_open; + phy->close = emac_sgmii_close; + phy->link_up = emac_sgmii_link_up; + phy->link_down = emac_sgmii_link_down; + /* Base address is the first address */ res = platform_get_resource(sgmii_pdev, IORESOURCE_MEM, 0); if (!res) { @@ -286,7 +373,11 @@ int emac_sgmii_config(struct platform_device *pdev, struct emac_adapter *adpt) if (ret) goto error; - emac_sgmii_irq_clear(adpt, SGMII_PHY_INTERRUPT_ERR); + emac_sgmii_link_init(adpt); + + ret = platform_get_irq(sgmii_pdev, 0); + if (ret > 0) + phy->irq = ret; /* We've remapped the addresses, so we don't need the device any * more. of_find_device_by_node() says we should release it. |