| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2019 MediaTek Inc. |
| */ |
| #include <linux/module.h> |
| #include <linux/phy.h> |
| #include <linux/of.h> |
| |
| #define PHY_ID_88Q2110 0x002b0980 |
| |
| #define Q2110_PMA_PMD_CTRL (MII_ADDR_C45 | 0x10000) |
| /* 1 = PMA/PMD reset, 0 = Normal */ |
| #define Q2110_PMA_PMD_RST BIT(15) |
| |
| #define Q2110_T1_PMA_PMD_CTRL (MII_ADDR_C45 | 0x10834) |
| /* 1 = PHY as master, 0 = PHY as slave */ |
| #define Q2110_T1_MASTER_SLAVE_CFG BIT(14) |
| /* 0 = 100BASE-T1, 1 = 1000BASE-T1 */ |
| #define Q2110_T1_LINK_TYPE BIT(0) |
| |
| #define Q2110_RST_CTRL (MII_ADDR_C45 | 0x038000) |
| /* software reset of T-unit */ |
| #define Q2110_RGMII_SW_RESET BIT(15) |
| |
| #define Q2110_PCS_CTRL (MII_ADDR_C45 | 0x030900) |
| #define Q2110_LOOPBACK BIT(14) |
| |
| #define Q2110_T1_AN_STATUS (MII_ADDR_C45 | 0x070201) |
| /* 1 = Link Up, 0 = Link Down */ |
| #define Q2110_T1_LINK_STATUS BIT(2) |
| |
| #define Q2110_COM_PORT_CTRL (MII_ADDR_C45 | 0x1f8001) |
| /* Add delay on TX_CLK */ |
| #define Q2110_RGMII_TX_TIMING_CTRL BIT(15) |
| /* Add delay on RX_CLK */ |
| #define Q2110_RGMII_RX_TIMING_CTRL BIT(14) |
| |
| /* Set and/or override some configuration registers based on the |
| * marvell,88q2110 property stored in the of_node for the phydev. |
| * marvell,88q2110 = <speed master>,...; |
| * speed: 1000Mbps or 100Mbps. |
| * master: 1-master, 0-slave. |
| */ |
| static int q2110_dts_init(struct phy_device *phydev) |
| { |
| const __be32 *paddr; |
| u32 speed; |
| u32 master; |
| int val, len; |
| |
| if (!phydev->mdio.dev.of_node) |
| return 0; |
| |
| paddr = of_get_property(phydev->mdio.dev.of_node, |
| "marvell,88q2110", &len); |
| if (!paddr) |
| return 0; |
| |
| speed = be32_to_cpup(paddr); |
| master = be32_to_cpup(paddr + 1); |
| |
| val = phy_read(phydev, Q2110_T1_PMA_PMD_CTRL); |
| if (val < 0) |
| return val; |
| val &= ~(Q2110_T1_MASTER_SLAVE_CFG | Q2110_T1_LINK_TYPE); |
| if (speed == SPEED_1000) |
| val |= Q2110_T1_LINK_TYPE; |
| if (master) |
| val |= Q2110_T1_MASTER_SLAVE_CFG; |
| val = phy_write(phydev, Q2110_T1_PMA_PMD_CTRL, val); |
| if (val < 0) |
| return val; |
| |
| /* Software Reset PHY */ |
| val = phy_read(phydev, Q2110_PMA_PMD_CTRL); |
| if (val < 0) |
| return val; |
| val |= Q2110_PMA_PMD_RST; |
| val = phy_write(phydev, Q2110_PMA_PMD_CTRL, val); |
| if (val < 0) |
| return val; |
| |
| do { |
| val = phy_read(phydev, Q2110_PMA_PMD_CTRL); |
| if (val < 0) |
| return val; |
| } while (val & Q2110_PMA_PMD_RST); |
| |
| return 0; |
| } |
| |
| static int q2110_timing_init(struct phy_device *phydev) |
| { |
| int val; |
| |
| if (phy_interface_is_rgmii(phydev)) { |
| val = phy_read(phydev, Q2110_COM_PORT_CTRL); |
| if (val < 0) |
| return val; |
| |
| val &= ~(Q2110_RGMII_TX_TIMING_CTRL | |
| Q2110_RGMII_RX_TIMING_CTRL); |
| |
| if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) |
| val |= (Q2110_RGMII_TX_TIMING_CTRL | |
| Q2110_RGMII_RX_TIMING_CTRL); |
| else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) |
| val |= Q2110_RGMII_RX_TIMING_CTRL; |
| else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) |
| val |= Q2110_RGMII_TX_TIMING_CTRL; |
| |
| val = phy_write(phydev, Q2110_COM_PORT_CTRL, val); |
| if (val < 0) |
| return val; |
| } |
| |
| /* Software Reset of T-unit */ |
| val = phy_read(phydev, Q2110_RST_CTRL); |
| if (val < 0) |
| return val; |
| val |= Q2110_RGMII_SW_RESET; |
| val = phy_write(phydev, Q2110_RST_CTRL, val); |
| if (val < 0) |
| return val; |
| |
| /* not self-clearing */ |
| val = phy_read(phydev, Q2110_RST_CTRL); |
| if (val < 0) |
| return val; |
| val &= ~Q2110_RGMII_SW_RESET; |
| val = phy_write(phydev, Q2110_RST_CTRL, val); |
| if (val < 0) |
| return val; |
| |
| return 0; |
| } |
| |
| static int q2110_config_init(struct phy_device *phydev) |
| { |
| phydev->supported = |
| SUPPORTED_1000baseT_Full | SUPPORTED_100baseT_Full; |
| phydev->advertising = |
| SUPPORTED_1000baseT_Full | SUPPORTED_100baseT_Full; |
| phydev->state = PHY_NOLINK; |
| phydev->autoneg = AUTONEG_DISABLE; |
| |
| q2110_dts_init(phydev); |
| q2110_timing_init(phydev); |
| |
| return 0; |
| } |
| |
| static int q2110_read_status(struct phy_device *phydev) |
| { |
| int val; |
| |
| phydev->duplex = 1; |
| phydev->pause = 0; |
| |
| val = phy_read(phydev, Q2110_T1_AN_STATUS); |
| if (val < 0) |
| return val; |
| |
| if (val & Q2110_T1_LINK_STATUS) |
| phydev->link = 1; |
| else |
| phydev->link = 0; |
| |
| val = phy_read(phydev, Q2110_T1_PMA_PMD_CTRL); |
| if (val < 0) |
| return val; |
| |
| if (val & Q2110_T1_LINK_TYPE) |
| phydev->speed = SPEED_1000; |
| else |
| phydev->speed = SPEED_100; |
| |
| return 0; |
| } |
| |
| static int q2110_match_phy_device(struct phy_device *phydev) |
| { |
| return (phydev->c45_ids.device_ids[1] & 0xfffffff0) == PHY_ID_88Q2110; |
| } |
| |
| static int q2110_loopback(struct phy_device *phydev, bool enable) |
| { |
| int val; |
| |
| val = phy_read(phydev, Q2110_PCS_CTRL); |
| if (val < 0) |
| return val; |
| |
| val &= ~Q2110_LOOPBACK; |
| if (enable) |
| val |= Q2110_LOOPBACK; |
| |
| val = phy_write(phydev, Q2110_PCS_CTRL, val); |
| if (val < 0) |
| return val; |
| |
| return 0; |
| } |
| |
| static struct phy_driver marvell_88q_driver[] = { |
| { |
| .phy_id = PHY_ID_88Q2110, |
| .phy_id_mask = 0xfffffff0, |
| .name = "Marvell 88Q2110", |
| .config_init = q2110_config_init, |
| .match_phy_device = q2110_match_phy_device, |
| .read_status = q2110_read_status, |
| .set_loopback = q2110_loopback, |
| } |
| }; |
| |
| module_phy_driver(marvell_88q_driver); |