| // 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 <a.shimko.dev@gmail.com> | |
| */ | |
| #include <linux/bitfield.h> | |
| #include <linux/errno.h> | |
| #include <linux/ethtool.h> | |
| #include <linux/kernel.h> | |
| #include <linux/iopoll.h> | |
| #include <linux/mii.h> | |
| #include <linux/module.h> | |
| #include <linux/netdevice.h> | |
| #include <linux/phy.h> | |
| #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 <a.shimko.dev@gmail.com>"); | |
| 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); | |