From 630ee8f358d961a7c8295d60a112e27cbfe4478d Mon Sep 17 00:00:00 2001 From: Florin Chiculita Date: Fri, 9 Nov 2018 06:20:36 +0200 Subject: [PATCH] net/phy: Inphi IN112525_s03 retimer support Software controller for IN112525_s03 retimer Signed-off-by: Florin Chiculita --- drivers/net/phy/Kconfig | 5 + drivers/net/phy/Makefile | 1 + drivers/net/phy/inphi.c | 578 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 584 insertions(+) create mode 100644 drivers/net/phy/inphi.c --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -475,6 +475,11 @@ config ICPLUS_PHY ---help--- Currently supports the IP175C and IP1001 PHYs. +config INPHI_PHY + tristate "Inphi CDR 10G/25G Ethernet PHY" + ---help--- + Currently supports the IN112525_S03 part @ 25G + config INTEL_XWAY_PHY tristate "Intel XWAY PHYs" ---help--- --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -87,6 +87,7 @@ obj-$(CONFIG_DP83848_PHY) += dp83848.o obj-$(CONFIG_DP83867_PHY) += dp83867.o obj-$(CONFIG_FIXED_PHY) += fixed_phy.o obj-$(CONFIG_ICPLUS_PHY) += icplus.o +obj-$(CONFIG_INPHI_PHY) += inphi.o obj-$(CONFIG_INTEL_XWAY_PHY) += intel-xway.o obj-$(CONFIG_LSI_ET1011C_PHY) += et1011c.o obj-$(CONFIG_LXT_PHY) += lxt.o --- /dev/null +++ b/drivers/net/phy/inphi.c @@ -0,0 +1,578 @@ +/* + * 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);