/* * Copyright 2018 NXP * Copyright 2018 INPHI * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * Inphi is a registered trademark of Inphi Corporation * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PHY_ID_IN112525 0x02107440 #define INPHI_S03_DEVICE_ID_MSB 0x2 #define INPHI_S03_DEVICE_ID_LSB 0x3 #define ALL_LANES 4 #define INPHI_POLL_DELAY 2500 #define PHYCTRL_REG1 0x0012 #define PHYCTRL_REG2 0x0014 #define PHYCTRL_REG3 0x0120 #define PHYCTRL_REG4 0x0121 #define PHYCTRL_REG5 0x0180 #define PHYCTRL_REG6 0x0580 #define PHYCTRL_REG7 0x05C4 #define PHYCTRL_REG8 0x01C8 #define PHYCTRL_REG9 0x0521 #define PHYSTAT_REG1 0x0021 #define PHYSTAT_REG2 0x0022 #define PHYSTAT_REG3 0x0123 #define PHYMISC_REG1 0x0025 #define PHYMISC_REG2 0x002c #define PHYMISC_REG3 0x00b3 #define PHYMISC_REG4 0x0181 #define PHYMISC_REG5 0x019D #define PHYMISC_REG6 0x0198 #define PHYMISC_REG7 0x0199 #define PHYMISC_REG8 0x0581 #define PHYMISC_REG9 0x0598 #define PHYMISC_REG10 0x059c #define PHYMISC_REG20 0x01B0 #define PHYMISC_REG21 0x01BC #define PHYMISC_REG22 0x01C0 #define RX_VCO_CODE_OFFSET 5 #define mdio_wr(a, b) phy_write_mmd(inphi_phydev, MDIO_MMD_VEND1, (a), (b)) #define mdio_rd(a) phy_read_mmd(inphi_phydev, MDIO_MMD_VEND1, (a)) #define VCO_CODE 390 int vco_codes[ALL_LANES] = { VCO_CODE, VCO_CODE, VCO_CODE, VCO_CODE }; static void mykmod_work_handler(struct work_struct *w); static struct workqueue_struct *wq; static DECLARE_DELAYED_WORK(mykmod_work, mykmod_work_handler); static unsigned long onesec; struct phy_device *inphi_phydev; int bit_test(int value, int bit_field) { int result; int bit_mask = (1 << bit_field); result = ((value & bit_mask) == bit_mask); return result; } int tx_pll_lock_test(int lane) { int i, val, locked = 1; if (lane == ALL_LANES) { for (i = 0; i < ALL_LANES; i++) { val = mdio_rd(i * 0x100 + PHYSTAT_REG3); locked = locked & bit_test(val, 15); } } else { val = mdio_rd(lane * 0x100 + PHYSTAT_REG3); locked = locked & bit_test(val, 15); } return locked; } void rx_reset_assert(int lane) { int mask, val; if (lane == ALL_LANES) { val = mdio_rd(PHYMISC_REG2); mask = (1 << 15); mdio_wr(PHYMISC_REG2, val + mask); } else { val = mdio_rd(lane * 0x100 + PHYCTRL_REG8); mask = (1 << 6); mdio_wr(lane * 0x100 + PHYCTRL_REG8, val + mask); } } void rx_reset_de_assert(int lane) { int mask, val; if (lane == ALL_LANES) { val = mdio_rd(PHYMISC_REG2); mask = 0xffff - (1 << 15); mdio_wr(PHYMISC_REG2, val & mask); } else { val = mdio_rd(lane * 0x100 + PHYCTRL_REG8); mask = 0xffff - (1 << 6); mdio_wr(lane * 0x100 + PHYCTRL_REG8, val & mask); } } void rx_powerdown_assert(int lane) { int mask, val; val = mdio_rd(lane * 0x100 + PHYCTRL_REG8); mask = (1 << 5); mdio_wr(lane * 0x100 + PHYCTRL_REG8, val + mask); } void rx_powerdown_de_assert(int lane) { int mask, val; val = mdio_rd(lane * 0x100 + PHYCTRL_REG8); mask = 0xffff - (1 << 5); mdio_wr(lane * 0x100 + PHYCTRL_REG8, val & mask); } void tx_pll_assert(int lane) { int val, recal; if (lane == ALL_LANES) { val = mdio_rd(PHYMISC_REG2); recal = (1 << 12); mdio_wr(PHYMISC_REG2, val | recal); } else { val = mdio_rd(lane * 0x100 + PHYCTRL_REG4); recal = (1 << 15); mdio_wr(lane * 0x100 + PHYCTRL_REG4, val | recal); } } void tx_pll_de_assert(int lane) { int recal, val; if (lane == ALL_LANES) { val = mdio_rd(PHYMISC_REG2); recal = 0xefff; mdio_wr(PHYMISC_REG2, val & recal); } else { val = mdio_rd(lane * 0x100 + PHYCTRL_REG4); recal = 0x7fff; mdio_wr(lane * 0x100 + PHYCTRL_REG4, val & recal); } } void tx_core_assert(int lane) { int recal, val, val2, core_reset; if (lane == 4) { val = mdio_rd(PHYMISC_REG2); recal = 1 << 10; mdio_wr(PHYMISC_REG2, val | recal); } else { val2 = mdio_rd(PHYMISC_REG3); core_reset = (1 << (lane + 8)); mdio_wr(PHYMISC_REG3, val2 | core_reset); } } void lol_disable(int lane) { int val, mask; val = mdio_rd(PHYMISC_REG3); mask = 1 << (lane + 4); mdio_wr(PHYMISC_REG3, val | mask); } void tx_core_de_assert(int lane) { int val, recal, val2, core_reset; if (lane == ALL_LANES) { val = mdio_rd(PHYMISC_REG2); recal = 0xffff - (1 << 10); mdio_wr(PHYMISC_REG2, val & recal); } else { val2 = mdio_rd(PHYMISC_REG3); core_reset = 0xffff - (1 << (lane + 8)); mdio_wr(PHYMISC_REG3, val2 & core_reset); } } void tx_restart(int lane) { tx_core_assert(lane); tx_pll_assert(lane); tx_pll_de_assert(lane); usleep_range(1500, 1600); tx_core_de_assert(lane); } void disable_lane(int lane) { rx_reset_assert(lane); rx_powerdown_assert(lane); tx_core_assert(lane); lol_disable(lane); } void toggle_reset(int lane) { int reg, val, orig; if (lane == ALL_LANES) { mdio_wr(PHYMISC_REG2, 0x8000); udelay(100); mdio_wr(PHYMISC_REG2, 0x0000); } else { reg = lane * 0x100 + PHYCTRL_REG8; val = (1 << 6); orig = mdio_rd(reg); mdio_wr(reg, orig + val); udelay(100); mdio_wr(reg, orig); } } int az_complete_test(int lane) { int success = 1, value; if (lane == 0 || lane == ALL_LANES) { value = mdio_rd(PHYCTRL_REG5); success = success & bit_test(value, 2); } if (lane == 1 || lane == ALL_LANES) { value = mdio_rd(PHYCTRL_REG5 + 0x100); success = success & bit_test(value, 2); } if (lane == 2 || lane == ALL_LANES) { value = mdio_rd(PHYCTRL_REG5 + 0x200); success = success & bit_test(value, 2); } if (lane == 3 || lane == ALL_LANES) { value = mdio_rd(PHYCTRL_REG5 + 0x300); success = success & bit_test(value, 2); } return success; } void save_az_offsets(int lane) { int i; #define AZ_OFFSET_LANE_UPDATE(reg, lane) \ mdio_wr((reg) + (lane) * 0x100, \ (mdio_rd((reg) + (lane) * 0x100) >> 8)) if (lane == ALL_LANES) { for (i = 0; i < ALL_LANES; i++) { AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20, i); AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 1, i); AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 2, i); AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 3, i); AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21, i); AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 1, i); AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 2, i); AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 3, i); AZ_OFFSET_LANE_UPDATE(PHYMISC_REG22, i); } } else { AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20, lane); AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 1, lane); AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 2, lane); AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 3, lane); AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21, lane); AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 1, lane); AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 2, lane); AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 3, lane); AZ_OFFSET_LANE_UPDATE(PHYMISC_REG22, lane); } mdio_wr(PHYCTRL_REG7, 0x0001); } void save_vco_codes(int lane) { int i; if (lane == ALL_LANES) { for (i = 0; i < ALL_LANES; i++) { vco_codes[i] = mdio_rd(PHYMISC_REG5 + i * 0x100); mdio_wr(PHYMISC_REG5 + i * 0x100, vco_codes[i] + RX_VCO_CODE_OFFSET); } } else { vco_codes[lane] = mdio_rd(PHYMISC_REG5 + lane * 0x100); mdio_wr(PHYMISC_REG5 + lane * 0x100, vco_codes[lane] + RX_VCO_CODE_OFFSET); } } int inphi_lane_recovery(int lane) { int i, value, az_pass; switch (lane) { case 0: case 1: case 2: case 3: rx_reset_assert(lane); mdelay(20); break; case ALL_LANES: mdio_wr(PHYMISC_REG2, 0x9C00); mdelay(20); do { value = mdio_rd(PHYMISC_REG2); udelay(10); } while (!bit_test(value, 4)); break; default: dev_err(&inphi_phydev->mdio.dev, "Incorrect usage of APIs in %s driver\n", inphi_phydev->drv->name); break; } if (lane == ALL_LANES) { for (i = 0; i < ALL_LANES; i++) mdio_wr(PHYMISC_REG7 + i * 0x100, VCO_CODE); } else { mdio_wr(PHYMISC_REG7 + lane * 0x100, VCO_CODE); } if (lane == ALL_LANES) for (i = 0; i < ALL_LANES; i++) mdio_wr(PHYCTRL_REG5 + i * 0x100, 0x0418); else mdio_wr(PHYCTRL_REG5 + lane * 0x100, 0x0418); mdio_wr(PHYCTRL_REG7, 0x0000); rx_reset_de_assert(lane); if (lane == ALL_LANES) { for (i = 0; i < ALL_LANES; i++) { mdio_wr(PHYCTRL_REG5 + i * 0x100, 0x0410); mdio_wr(PHYCTRL_REG5 + i * 0x100, 0x0412); } } else { mdio_wr(PHYCTRL_REG5 + lane * 0x100, 0x0410); mdio_wr(PHYCTRL_REG5 + lane * 0x100, 0x0412); } for (i = 0; i < 64; i++) { mdelay(100); az_pass = az_complete_test(lane); if (az_pass) { save_az_offsets(lane); break; } } if (!az_pass) { pr_info("in112525: AZ calibration fail @ lane=%d\n", lane); return -1; } if (lane == ALL_LANES) { mdio_wr(PHYMISC_REG8, 0x0002); mdio_wr(PHYMISC_REG9, 0x2028); mdio_wr(PHYCTRL_REG6, 0x0010); usleep_range(1000, 1200); mdio_wr(PHYCTRL_REG6, 0x0110); mdelay(30); mdio_wr(PHYMISC_REG9, 0x3020); } else { mdio_wr(PHYMISC_REG4 + lane * 0x100, 0x0002); mdio_wr(PHYMISC_REG6 + lane * 0x100, 0x2028); mdio_wr(PHYCTRL_REG5 + lane * 0x100, 0x0010); usleep_range(1000, 1200); mdio_wr(PHYCTRL_REG5 + lane * 0x100, 0x0110); mdelay(30); mdio_wr(PHYMISC_REG6 + lane * 0x100, 0x3020); } if (lane == ALL_LANES) { mdio_wr(PHYMISC_REG2, 0x1C00); mdio_wr(PHYMISC_REG2, 0x0C00); } else { tx_restart(lane); mdelay(11); } if (lane == ALL_LANES) { if (bit_test(mdio_rd(PHYMISC_REG2), 6) == 0) return -1; } else { if (tx_pll_lock_test(lane) == 0) return -1; } save_vco_codes(lane); if (lane == ALL_LANES) { mdio_wr(PHYMISC_REG2, 0x0400); mdio_wr(PHYMISC_REG2, 0x0000); value = mdio_rd(PHYCTRL_REG1); value = value & 0xffbf; mdio_wr(PHYCTRL_REG2, value); } else { tx_core_de_assert(lane); } if (lane == ALL_LANES) { mdio_wr(PHYMISC_REG1, 0x8000); mdio_wr(PHYMISC_REG1, 0x0000); } mdio_rd(PHYMISC_REG1); mdio_rd(PHYMISC_REG1); usleep_range(1000, 1200); mdio_rd(PHYSTAT_REG1); mdio_rd(PHYSTAT_REG2); return 0; } static void mykmod_work_handler(struct work_struct *w) { int all_lanes_lock, lane0_lock, lane1_lock, lane2_lock, lane3_lock; lane0_lock = bit_test(mdio_rd(0x123), 15); lane1_lock = bit_test(mdio_rd(0x223), 15); lane2_lock = bit_test(mdio_rd(0x323), 15); lane3_lock = bit_test(mdio_rd(0x423), 15); /* check if the chip had any successful lane lock from the previous * stage (e.g. u-boot) */ all_lanes_lock = lane0_lock | lane1_lock | lane2_lock | lane3_lock; if (!all_lanes_lock) { /* start fresh */ inphi_lane_recovery(ALL_LANES); } else { if (!lane0_lock) inphi_lane_recovery(0); if (!lane1_lock) inphi_lane_recovery(1); if (!lane2_lock) inphi_lane_recovery(2); if (!lane3_lock) inphi_lane_recovery(3); } queue_delayed_work(wq, &mykmod_work, onesec); } int inphi_probe(struct phy_device *phydev) { int phy_id = 0, id_lsb = 0, id_msb = 0; /* Read device id from phy registers */ id_lsb = phy_read_mmd(phydev, MDIO_MMD_VEND1, INPHI_S03_DEVICE_ID_MSB); if (id_lsb < 0) return -ENXIO; phy_id = id_lsb << 16; id_msb = phy_read_mmd(phydev, MDIO_MMD_VEND1, INPHI_S03_DEVICE_ID_LSB); if (id_msb < 0) return -ENXIO; phy_id |= id_msb; /* Make sure the device tree binding matched the driver with the * right device. */ if (phy_id != phydev->drv->phy_id) { dev_err(&phydev->mdio.dev, "Error matching phy with %s driver\n", phydev->drv->name); return -ENODEV; } /* update the local phydev pointer, used inside all APIs */ inphi_phydev = phydev; onesec = msecs_to_jiffies(INPHI_POLL_DELAY); wq = create_singlethread_workqueue("inphi_kmod"); if (wq) { queue_delayed_work(wq, &mykmod_work, onesec); } else { dev_err(&phydev->mdio.dev, "Error creating kernel workqueue for %s driver\n", phydev->drv->name); return -ENOMEM; } return 0; } static struct phy_driver inphi_driver[] = { { .phy_id = PHY_ID_IN112525, .phy_id_mask = 0x0ff0fff0, .name = "Inphi 112525_S03", .features = PHY_GBIT_FEATURES, .probe = &inphi_probe, }, }; module_phy_driver(inphi_driver); static struct mdio_device_id __maybe_unused inphi_tbl[] = { { PHY_ID_IN112525, 0x0ff0fff0}, {}, }; MODULE_DEVICE_TABLE(mdio, inphi_tbl);