| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2022 Bootlin |
| * |
| * Maxime Chevallier <maxime.chevallier@bootlin.com> |
| */ |
| |
| #include <linux/netdevice.h> |
| #include <linux/phy.h> |
| #include <linux/phylink.h> |
| #include <linux/pcs-altera-tse.h> |
| |
| /* SGMII PCS register addresses |
| */ |
| #define SGMII_PCS_LINK_TIMER_0 0x12 |
| #define SGMII_PCS_LINK_TIMER_1 0x13 |
| #define SGMII_PCS_IF_MODE 0x14 |
| #define PCS_IF_MODE_SGMII_ENA BIT(0) |
| #define PCS_IF_MODE_USE_SGMII_AN BIT(1) |
| #define PCS_IF_MODE_SGMI_HALF_DUPLEX BIT(4) |
| #define PCS_IF_MODE_SGMI_PHY_AN BIT(5) |
| #define SGMII_PCS_SW_RESET_TIMEOUT 100 /* usecs */ |
| |
| struct altera_tse_pcs { |
| struct phylink_pcs pcs; |
| void __iomem *base; |
| int reg_width; |
| }; |
| |
| static struct altera_tse_pcs *phylink_pcs_to_tse_pcs(struct phylink_pcs *pcs) |
| { |
| return container_of(pcs, struct altera_tse_pcs, pcs); |
| } |
| |
| static u16 tse_pcs_read(struct altera_tse_pcs *tse_pcs, int regnum) |
| { |
| if (tse_pcs->reg_width == 4) |
| return readl(tse_pcs->base + regnum * 4); |
| else |
| return readw(tse_pcs->base + regnum * 2); |
| } |
| |
| static void tse_pcs_write(struct altera_tse_pcs *tse_pcs, int regnum, |
| u16 value) |
| { |
| if (tse_pcs->reg_width == 4) |
| writel(value, tse_pcs->base + regnum * 4); |
| else |
| writew(value, tse_pcs->base + regnum * 2); |
| } |
| |
| static int tse_pcs_reset(struct altera_tse_pcs *tse_pcs) |
| { |
| u16 bmcr; |
| |
| /* Reset PCS block */ |
| bmcr = tse_pcs_read(tse_pcs, MII_BMCR); |
| bmcr |= BMCR_RESET; |
| tse_pcs_write(tse_pcs, MII_BMCR, bmcr); |
| |
| return read_poll_timeout(tse_pcs_read, bmcr, (bmcr & BMCR_RESET), |
| 10, SGMII_PCS_SW_RESET_TIMEOUT, 1, |
| tse_pcs, MII_BMCR); |
| } |
| |
| static int alt_tse_pcs_validate(struct phylink_pcs *pcs, |
| unsigned long *supported, |
| const struct phylink_link_state *state) |
| { |
| if (state->interface == PHY_INTERFACE_MODE_SGMII || |
| state->interface == PHY_INTERFACE_MODE_1000BASEX) |
| return 1; |
| |
| return -EINVAL; |
| } |
| |
| static int alt_tse_pcs_config(struct phylink_pcs *pcs, unsigned int mode, |
| phy_interface_t interface, |
| const unsigned long *advertising, |
| bool permit_pause_to_mac) |
| { |
| struct altera_tse_pcs *tse_pcs = phylink_pcs_to_tse_pcs(pcs); |
| u32 ctrl, if_mode; |
| |
| ctrl = tse_pcs_read(tse_pcs, MII_BMCR); |
| if_mode = tse_pcs_read(tse_pcs, SGMII_PCS_IF_MODE); |
| |
| /* Set link timer to 1.6ms, as per the MegaCore Function User Guide */ |
| tse_pcs_write(tse_pcs, SGMII_PCS_LINK_TIMER_0, 0x0D40); |
| tse_pcs_write(tse_pcs, SGMII_PCS_LINK_TIMER_1, 0x03); |
| |
| if (interface == PHY_INTERFACE_MODE_SGMII) { |
| if_mode |= PCS_IF_MODE_USE_SGMII_AN | PCS_IF_MODE_SGMII_ENA; |
| } else if (interface == PHY_INTERFACE_MODE_1000BASEX) { |
| if_mode &= ~(PCS_IF_MODE_USE_SGMII_AN | PCS_IF_MODE_SGMII_ENA); |
| } |
| |
| ctrl |= (BMCR_SPEED1000 | BMCR_FULLDPLX | BMCR_ANENABLE); |
| |
| tse_pcs_write(tse_pcs, MII_BMCR, ctrl); |
| tse_pcs_write(tse_pcs, SGMII_PCS_IF_MODE, if_mode); |
| |
| return tse_pcs_reset(tse_pcs); |
| } |
| |
| static void alt_tse_pcs_get_state(struct phylink_pcs *pcs, |
| struct phylink_link_state *state) |
| { |
| struct altera_tse_pcs *tse_pcs = phylink_pcs_to_tse_pcs(pcs); |
| u16 bmsr, lpa; |
| |
| bmsr = tse_pcs_read(tse_pcs, MII_BMSR); |
| lpa = tse_pcs_read(tse_pcs, MII_LPA); |
| |
| phylink_mii_c22_pcs_decode_state(state, bmsr, lpa); |
| } |
| |
| static void alt_tse_pcs_an_restart(struct phylink_pcs *pcs) |
| { |
| struct altera_tse_pcs *tse_pcs = phylink_pcs_to_tse_pcs(pcs); |
| u16 bmcr; |
| |
| bmcr = tse_pcs_read(tse_pcs, MII_BMCR); |
| bmcr |= BMCR_ANRESTART; |
| tse_pcs_write(tse_pcs, MII_BMCR, bmcr); |
| |
| /* This PCS seems to require a soft reset to re-sync the AN logic */ |
| tse_pcs_reset(tse_pcs); |
| } |
| |
| static const struct phylink_pcs_ops alt_tse_pcs_ops = { |
| .pcs_validate = alt_tse_pcs_validate, |
| .pcs_get_state = alt_tse_pcs_get_state, |
| .pcs_config = alt_tse_pcs_config, |
| .pcs_an_restart = alt_tse_pcs_an_restart, |
| }; |
| |
| struct phylink_pcs *alt_tse_pcs_create(struct net_device *ndev, |
| void __iomem *pcs_base, int reg_width) |
| { |
| struct altera_tse_pcs *tse_pcs; |
| |
| if (reg_width != 4 && reg_width != 2) |
| return ERR_PTR(-EINVAL); |
| |
| tse_pcs = devm_kzalloc(&ndev->dev, sizeof(*tse_pcs), GFP_KERNEL); |
| if (!tse_pcs) |
| return ERR_PTR(-ENOMEM); |
| |
| tse_pcs->pcs.ops = &alt_tse_pcs_ops; |
| tse_pcs->base = pcs_base; |
| tse_pcs->reg_width = reg_width; |
| |
| return &tse_pcs->pcs; |
| } |
| EXPORT_SYMBOL_GPL(alt_tse_pcs_create); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_DESCRIPTION("Altera TSE PCS driver"); |
| MODULE_AUTHOR("Maxime Chevallier <maxime.chevallier@bootlin.com>"); |