| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2019 MediaTek Inc. |
| */ |
| #include <linux/module.h> |
| #include <linux/phy.h> |
| #include <linux/of.h> |
| //xf.li 2022/9/9 modify for LPSD mode start |
| #include <linux/of_gpio.h> |
| #include <linux/gpio/consumer.h> |
| //xf.li 2022/9/9 modify for LPSD mode end |
| #include <linux/delay.h> |
| |
| |
| #define PHY_ID_88Q2220_B0 0x002b0b21 |
| #define PHY_ID_88Q2220_B1 0x002b0b22 |
| |
| #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 | 0x4A001) |
| /* 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) |
| |
| /* NAD_WAKEUP_PHY1 : GPIO7 */ |
| #define NAD_WAKEUP_PHY1 275 |
| /* NAD_RESET_PHY1 : GPIO26 */ |
| #define NAD_RESET_PHY1 294 |
| /* PHY_POWER_SUPPLY : GPIO178 */ |
| #define PHY_POWER_SUPPLY 446 |
| |
| #define Q2220_LPSD_CTRL_1 (MII_ADDR_C45 | 0x038021) |
| #define Q2220_LPSD_DISABLE_REMOTE_WAKE_UP BIT(15) | BIT(11) |
| #define Q2220_LPSD_STATUS (MII_ADDR_C45 | 0x038020) |
| #define Q2220_LPSD_POWER_DOWN BIT(0) |
| |
| int if_suspend = 0; |
| |
| /* 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,88q2220", &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; |
| /* |
| phy_write(phydev, (MII_ADDR_C45 | 0x030900), 0x8000); |
| phy_write(phydev, (MII_ADDR_C45 | 0x03FFE4), 0x000C); |
| */ |
| printk("q2110 dts init ok!!\n"); |
| |
| 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; |
| } |
| |
| |
| 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; |
| |
| 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 q2220_b0_config_init(struct phy_device *phydev) |
| { |
| int i=0,mrvlId; |
| phydev->supported = |
| SUPPORTED_1000baseT_Full | SUPPORTED_100baseT_Full; |
| phydev->advertising = |
| SUPPORTED_1000baseT_Full | SUPPORTED_100baseT_Full; |
| phydev->state = PHY_NOLINK; |
| phydev->autoneg = AUTONEG_DISABLE; |
| |
| printk("PHY_DEVICE is B0"); |
| phy_write(phydev, MII_ADDR_C45 | 0x038033, 0x6801); |
| phy_write(phydev, MII_ADDR_C45 | 0x070200, 0x0000U); |
| phy_write(phydev, MII_ADDR_C45 | 0x010000, 0x840); |
| phy_write(phydev, MII_ADDR_C45 | 0x03FE1B, 0x48); |
| phy_write(phydev, MII_ADDR_C45 | 0x01FFE4, 0x6B6); |
| phy_write(phydev, MII_ADDR_C45 | 0x010000, 0x0); |
| phy_write(phydev, MII_ADDR_C45 | 0x030000, 0x0); |
| while (i < 5) { |
| mrvlId= phy_read(phydev, MII_ADDR_C45 | 0x03FFE4); |
| if (mrvlId == 0x06BA) { |
| break; |
| } |
| i++; |
| msleep(1); |
| } |
| phy_write(phydev, MII_ADDR_C45 | 0x078032, 0x2020); |
| phy_write(phydev, MII_ADDR_C45 | 0x078031, 0xA28); |
| phy_write(phydev, MII_ADDR_C45 | 0x078031, 0xC28); |
| phy_write(phydev, MII_ADDR_C45 | 0x03FFDB, 0xFC10); |
| phy_write(phydev, MII_ADDR_C45 | 0x03FE1B, 0x58); |
| //cheng.c modify for ethernet can not link up with golden device |
| phy_write(phydev, MII_ADDR_C45 | 0x03803A, 0xDA44); |
| phy_write(phydev, MII_ADDR_C45 | 0x038039, 0x2C0B); |
| |
| phy_write(phydev, MII_ADDR_C45 | 0x038027, 0x1);//TC10 sleep/wakeup capability support |
| q2110_timing_init(phydev); |
| |
| q2110_dts_init(phydev); |
| |
| //softResetGe |
| phy_write(phydev, (MII_ADDR_C45 | 0x03FE1B), 0x48); |
| phy_write(phydev, (MII_ADDR_C45 | 0x030900), 0x8000); |
| phy_write(phydev, (MII_ADDR_C45 | 0x03FFE4), 0x000C); |
| phy_write(phydev, (MII_ADDR_C45 | 0x03FE1B), 0x58); |
| |
| #if 0 |
| //FE/100BT1 |
| phy_write(phydev, (MII_ADDR_C45 | 0x030900), 0x8000); |
| phy_write(phydev, (MII_ADDR_C45 | 0x03FFE4), 0x000C); |
| #endif |
| printk("phy init ok\n"); |
| return 0; |
| } |
| |
| static int q2220_b1_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; |
| |
| |
| printk("PHY_DEVICE is B1"); |
| /* set power management state breakpoint (internal state) */ |
| phy_write(phydev, MII_ADDR_C45 | 0x03FFE4, 0x0007); |
| /* disable ANEG */ |
| phy_write(phydev, MII_ADDR_C45 | 0x070200, 0x0000U); |
| /* prepare to write init code */ |
| /* enable TXDAC setting overwrite bit */ |
| phy_write(phydev, MII_ADDR_C45 | 0x03ffe3, 0x7000); |
| /* set IEEE power down */ |
| phy_write(phydev, MII_ADDR_C45 | 0x010000, 0x0840); |
| /* wait 3 ms to get to ON_TBGB State and send out WUP if needed */ |
| mdelay(3); |
| /* exit standby state(internal state) */ |
| phy_write(phydev, MII_ADDR_C45 | 0x03FE1B, 0x0048); |
| /* exit IEEE power down */ |
| phy_write(phydev, MII_ADDR_C45 | 0x010000, 0x0000); |
| phy_write(phydev, MII_ADDR_C45 | 0x030000, 0x0000); |
| /* configure TC10 loc_act_detect for rev B1 */ |
| phy_write(phydev, MII_ADDR_C45 | 0x03FCAD, 0x030C); |
| phy_write(phydev, MII_ADDR_C45 | 0x038032, 0x6001); |
| phy_write(phydev, MII_ADDR_C45 | 0x03FDFF, 0x05A5); |
| phy_write(phydev, MII_ADDR_C45 | 0x03FDEC, 0xDBAF); |
| phy_write(phydev, MII_ADDR_C45 | 0x03FCAB, 0x1054); |
| phy_write(phydev, MII_ADDR_C45 | 0x03FCAC, 0x1483); |
| /* set DISABLE_WAIT_COMM to 0 for WUR */ |
| phy_write(phydev, MII_ADDR_C45 | 0x038033, 0xC801); |
| /* send_s detection threshold, slave and master */ |
| phy_write(phydev, MII_ADDR_C45 | 0x78032, 0x2020); |
| phy_write(phydev, MII_ADDR_C45 | 0x78031, 0xA28); |
| phy_write(phydev, MII_ADDR_C45 | 0x78031, 0xC28); |
| /* disable DCL calibratin during tear down */ |
| phy_write(phydev, MII_ADDR_C45 | 0x03FFDB, 0xFC10); |
| /* disable RESET of DCL*/ |
| phy_write(phydev, MII_ADDR_C45 | 0x03FE1B, 0x58); |
| /* update Initial FFE Coefficients */ |
| phy_write(phydev, MII_ADDR_C45 | 0x03FBBA, 0x0CB2); |
| phy_write(phydev, MII_ADDR_C45 | 0x03FBBB, 0x0C4A); |
| /* Toggle Squelch override */ |
| phy_write(phydev, MII_ADDR_C45 | 0x048178, 0x7540); |
| phy_write(phydev, MII_ADDR_C45 | 0x048178, 0x5540); |
| /* aneg set pga gain and amp */ |
| phy_write(phydev, MII_ADDR_C45 | 0x03FE5F, 0xE8); |
| phy_write(phydev, MII_ADDR_C45 | 0x03FE05, 0x755C); |
| |
| phy_write(phydev, MII_ADDR_C45 | 0x03803A, 0xDA44); |
| phy_write(phydev, MII_ADDR_C45 | 0x038039, 0x2C0B); |
| |
| phy_write(phydev, MII_ADDR_C45 | 0x038027, 0x0);//TC10 sleep/wakeup capability disable |
| q2110_timing_init(phydev); |
| |
| q2110_dts_init(phydev); |
| |
| //softResetGe |
| phy_write(phydev, (MII_ADDR_C45 | 0x03FE1B), 0x48); |
| phy_write(phydev, (MII_ADDR_C45 | 0x030900), 0x8000); |
| phy_write(phydev, (MII_ADDR_C45 | 0x03FFE4), 0x000C); |
| phy_write(phydev, (MII_ADDR_C45 | 0x03FE1B), 0x58); |
| |
| #if 0 |
| //FE/100BT1 |
| phy_write(phydev, (MII_ADDR_C45 | 0x030900), 0x8000); |
| phy_write(phydev, (MII_ADDR_C45 | 0x03FFE4), 0x000C); |
| #endif |
| printk("phy init ok\n"); |
| return 0; |
| } |
| |
| 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 int q2110_read_status(struct phy_device *phydev) |
| { |
| int val; |
| unsigned int regVal1, regVal2, regVal3; |
| |
| phydev->duplex = 1; |
| phydev->pause = 0; |
| |
| regVal1 = phy_read(phydev, MII_ADDR_C45 | 0x030901); |
| regVal1 = phy_read(phydev, MII_ADDR_C45 | 0x030901); |
| regVal2 = phy_read(phydev, MII_ADDR_C45 | 0x078001); |
| regVal3 = phy_read(phydev, MII_ADDR_C45 | 0x03FD9D); |
| |
| phydev->link = ((0x0004U == (regVal1 & 0x0004U)) |
| && (0x3000U == (regVal2 & 0x3000U)) |
| && (0x0010U == (regVal3 & 0x0010U))) ? 1 : 0; |
| if(phydev->link == 0) |
| { |
| printk("phy_link_status down: regVal1: %x, regVal2: %x, regVal3: %x.\n",regVal1, regVal2, regVal3); |
| } |
| /* 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; |
| } |
| |
| int q2220_suspend(struct phy_device *phydev) |
| { |
| //xf.li 2022/11/9 modify for API-647 |
| printk("phy sleep start\n"); |
| gpio_direction_output(NAD_WAKEUP_PHY1, 0); |
| mdelay(1);//ensure do not wake up |
| //phy_write(phydev, MII_ADDR_C45 | 0x038022, 0x1); |
| phy_write(phydev, Q2220_LPSD_CTRL_1, Q2220_LPSD_DISABLE_REMOTE_WAKE_UP); |
| mdelay(10);//for enter T1 port sleep, need link parter respond |
| printk("reg 038021 = %x\n", phy_read(phydev, Q2220_LPSD_CTRL_1)); |
| phy_write(phydev, Q2220_LPSD_STATUS, Q2220_LPSD_POWER_DOWN);// enter LPSD sleep mode |
| printk("reg 038020 = %x\n", phy_read(phydev, Q2220_LPSD_STATUS)); |
| mdelay(50); |
| printk("reg 038020 = %x\n", phy_read(phydev, Q2220_LPSD_STATUS)); |
| gpio_direction_output(NAD_RESET_PHY1, 0); |
| mdelay(1); |
| gpio_direction_output(PHY_POWER_SUPPLY, 0); |
| if_suspend = 1; |
| return 0; |
| //xf.li 2022/11/9 modify for API-647 |
| } |
| int q2220_b0_resume(struct phy_device *phydev) |
| { |
| //xf.li 2022/11/9 modify for API-647 |
| if(if_suspend == 1) |
| { |
| printk("phy awake start\n"); |
| gpio_direction_output(PHY_POWER_SUPPLY, 1); |
| mdelay(1); |
| gpio_direction_output(NAD_WAKEUP_PHY1, 1); |
| mdelay(10); |
| gpio_direction_output(NAD_WAKEUP_PHY1, 0); |
| mdelay(5); |
| gpio_direction_output(NAD_RESET_PHY1, 1); |
| mdelay(10); |
| gpio_direction_output(NAD_RESET_PHY1, 0); |
| mdelay(30); |
| gpio_direction_output(NAD_RESET_PHY1, 1); |
| mdelay(10);//at lest 4ms for reset phy |
| q2220_b0_config_init(phydev); |
| if_suspend = 0; |
| } |
| else |
| { |
| printk("q2220_resume: no suspend! In boot state."); |
| } |
| return 0; |
| //xf.li 2022/11/9 modify for API-647 |
| } |
| |
| int q2220_b1_resume(struct phy_device *phydev) |
| { |
| //xf.li 2022/11/9 modify for API-647 |
| if(if_suspend == 1) |
| { |
| printk("phy awake start\n"); |
| gpio_direction_output(PHY_POWER_SUPPLY, 1); |
| mdelay(1); |
| gpio_direction_output(NAD_WAKEUP_PHY1, 1); |
| mdelay(10); |
| gpio_direction_output(NAD_WAKEUP_PHY1, 0); |
| mdelay(5); |
| gpio_direction_output(NAD_RESET_PHY1, 1); |
| mdelay(10); |
| gpio_direction_output(NAD_RESET_PHY1, 0); |
| mdelay(30); |
| gpio_direction_output(NAD_RESET_PHY1, 1); |
| mdelay(10);//at lest 4ms for reset phy |
| q2220_b1_config_init(phydev); |
| if_suspend = 0; |
| } |
| else |
| { |
| printk("q2220_resume: no suspend! In boot state."); |
| } |
| return 0; |
| //xf.li 2022/11/9 modify for API-647 |
| } |
| |
| |
| static int q2220_b0_match_phy_device(struct phy_device *phydev) |
| { |
| return ((phydev->c45_ids.device_ids[1] & 0xffffffff) == PHY_ID_88Q2220_B0); |
| } |
| |
| static int q2220_b1_match_phy_device(struct phy_device *phydev) |
| { |
| return ((phydev->c45_ids.device_ids[1] & 0xffffffff) == PHY_ID_88Q2220_B1); |
| } |
| |
| static struct phy_driver marvell_88q_driver[] = { |
| { |
| .phy_id = PHY_ID_88Q2220_B0, |
| .phy_id_mask = 0xfffffff0, |
| .name = "Marvell 88Q2220 B0", |
| .config_init = q2220_b0_config_init, |
| .match_phy_device = q2220_b0_match_phy_device, |
| .set_loopback = &q2110_loopback, |
| .read_status = q2110_read_status, |
| .suspend = q2220_suspend, |
| .resume = q2220_b0_resume, |
| }, { |
| .phy_id = PHY_ID_88Q2220_B1, |
| .phy_id_mask = 0xfffffff0, |
| .name = "Marvell 88Q2220 B1", |
| .config_init = q2220_b1_config_init, |
| .match_phy_device = q2220_b1_match_phy_device, |
| .set_loopback = &q2110_loopback, |
| .read_status = q2110_read_status, |
| .suspend = q2220_suspend, |
| .resume = q2220_b1_resume, |
| } |
| }; |
| |
| module_phy_driver(marvell_88q_driver); |