// SPDX-License-Identifier: GPL-2.0 /* * Driver for the DAPU Telecom DAP8211R(I) Gigabit Ethernet PHY. * * Specifications: * - IEEE 802.3 10BASE-Te, 100BASE-TX, 1000BASE-T * - IEEE 802.3az-2010 Energy Efficient Ethernet * - IEEE 1588 SyncE support * - RGMII * * Author: Artem Shimko */ #include #include #include #include #include #include #include #include #include #define DAP8211R_PHY_ID 0x0008011B #define DAP8211R_PHY_ID_MASK 0xFFFFFFFF #define DAP8211R_EXT_ADD 0x1E #define DAP8211R_EXT_DATA 0x1F #define DAP8211R_PHY_CON 0xA001 #define DAP8211R_PHY_SW_RST BIT(15) #define DAP8211R_RGMII_CON 0xA003 #define DAP8211R_RGMII_TX_DEL_MASK GENMASK(3, 0) #define DAP8211R_RGMII_RX_DEL_MASK GENMASK(13, 10) #define DAP8211R_RGMII_CONFIG_MASK (DAP8211R_RGMII_RX_DEL_MASK | DAP8211R_RGMII_TX_DEL_MASK) /* * Hardware reset RX RGMII default delay from the datasheet: 0 * 150ps == 0.00ns * Used when rx-internal-delay-ps is not specified in DT for RGMII mode and * in RGMII_TXID to set rx delay. */ #define DAP8211R_INITIAL_RX_DEL_VAL 0 /* * Hardware reset TX RGMII default delay from the datasheet: 1 * 150ps == 0.15ns * Used when tx-internal-delay-ps is not specified in DT for RGMII mode and * in RGMII_RXID to set tx delay. */ #define DAP8211R_INITIAL_TX_DEL_VAL 1 /* * Default RGMII delay: 13 * 150 == 1.95ns * Used when rx-internal-delay-ps or tx-internal-delay-ps are not specified in DT * for RGMII ID modes to set tx or rx delay. */ #define DAP8211R_DEFAULT_DEL_SEL 0xD static const int dap8211r_internal_delay[] = {0, 150, 300, 450, 600, 750, 900, 1050, 1200, 1350, 1500, 1650, 1800, 1950, 2100, 2250}; #define DAP8211R_DELAY_SIZE ARRAY_SIZE(dap8211r_internal_delay) /** * dap8211r_read_ext() - Read extended register * @phydev: PHY device structure * @reg: Extended register address * * Reads a PHY extended register using the indirect access method. * The caller must hold the MDIO bus lock. * * Return: Register value on success, or negative error code */ static int dap8211r_read_ext(struct phy_device *phydev, u16 reg) { int ret; phy_lock_mdio_bus(phydev); ret = __phy_write(phydev, DAP8211R_EXT_ADD, reg); if (ret < 0) goto out; ret = __phy_read(phydev, DAP8211R_EXT_DATA); out: phy_unlock_mdio_bus(phydev); return ret; } /** * dap8211r_modify_ext() - Modify extended register bits * @phydev: PHY device structure * @reg: Extended register address * @mask: Bit mask of bits to clear * @set: Bit mask of bits to set * * Modifies a PHY extended register using the indirect access method. * New value = (old value & ~mask) | set. * The caller must hold the MDIO bus lock. * * Return: 0 on success, or negative error code */ static int dap8211r_modify_ext(struct phy_device *phydev, u16 reg, u16 mask, u16 set) { int ret; phy_lock_mdio_bus(phydev); ret = __phy_write(phydev, DAP8211R_EXT_ADD, reg); if (ret < 0) goto out; ret = __phy_modify(phydev, DAP8211R_EXT_DATA, mask, set); out: phy_unlock_mdio_bus(phydev); return ret; } /** * dap8211r_config_init() - Initialize PHY * @phydev: PHY device structure * * Configures the PHY during initialization: * - RGMII delays based on interface mode * - Software reset to apply settings (low active, self clear) * * Return: 0 on success, or negative error code */ static int dap8211r_config_init(struct phy_device *phydev) { u16 set = 0; int ret, val; s32 rx_internal_delay = DAP8211R_INITIAL_RX_DEL_VAL; s32 tx_internal_delay = DAP8211R_INITIAL_TX_DEL_VAL; if (!phy_interface_is_rgmii(phydev)) return 0; if (phydev->interface != PHY_INTERFACE_MODE_RGMII_TXID) rx_internal_delay = phy_get_internal_delay(phydev, dap8211r_internal_delay, DAP8211R_DELAY_SIZE, true); if (phydev->interface != PHY_INTERFACE_MODE_RGMII_RXID) tx_internal_delay = phy_get_internal_delay(phydev, dap8211r_internal_delay, DAP8211R_DELAY_SIZE, false); switch (phydev->interface) { case PHY_INTERFACE_MODE_RGMII: if (rx_internal_delay < 0) rx_internal_delay = DAP8211R_INITIAL_RX_DEL_VAL; if (tx_internal_delay < 0) tx_internal_delay = DAP8211R_INITIAL_TX_DEL_VAL; break; case PHY_INTERFACE_MODE_RGMII_RXID: if (rx_internal_delay < 0) rx_internal_delay = DAP8211R_DEFAULT_DEL_SEL; break; case PHY_INTERFACE_MODE_RGMII_ID: if (rx_internal_delay < 0) rx_internal_delay = DAP8211R_DEFAULT_DEL_SEL; fallthrough; case PHY_INTERFACE_MODE_RGMII_TXID: if (tx_internal_delay < 0) tx_internal_delay = DAP8211R_DEFAULT_DEL_SEL; break; default: phydev_err(phydev, "Unsupported interface: %d\n", phydev->interface); return -EINVAL; } set |= FIELD_PREP(DAP8211R_RGMII_RX_DEL_MASK, rx_internal_delay); set |= FIELD_PREP(DAP8211R_RGMII_TX_DEL_MASK, tx_internal_delay); ret = dap8211r_modify_ext(phydev, DAP8211R_PHY_CON, DAP8211R_PHY_SW_RST, 0); if (ret) return ret; /* Wait for reset self-clear (from low active to high) */ ret = read_poll_timeout(dap8211r_read_ext, val, (val & DAP8211R_PHY_SW_RST), 20, 200, false, phydev, DAP8211R_PHY_CON); if (ret) return ret; if (val < 0) return val; ret = dap8211r_modify_ext(phydev, DAP8211R_RGMII_CON, DAP8211R_RGMII_CONFIG_MASK, set); if (ret) return ret; return 0; } static struct phy_driver dap8211r_driver[] = { { PHY_ID_MATCH_EXACT(DAP8211R_PHY_ID), .name = "DAP8211R Gigabit Ethernet", .soft_reset = genphy_soft_reset, .config_init = dap8211r_config_init, .read_status = genphy_read_status, .set_loopback = genphy_loopback, .config_aneg = genphy_config_aneg, .suspend = genphy_suspend, .resume = genphy_resume, }, }; module_phy_driver(dap8211r_driver); MODULE_DESCRIPTION("DAP8211R Gigabit Ethernet PHY driver"); MODULE_AUTHOR("Artem Shimko "); MODULE_LICENSE("GPL"); static const struct mdio_device_id __maybe_unused dap8211r_tb[] = { { DAP8211R_PHY_ID, DAP8211R_PHY_ID_MASK }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(mdio, dap8211r_tb);