net/phy: Inphi IN112525_s03 retimer support
authorFlorin Chiculita <florinlaurentiu.chiculita@nxp.com>
Fri, 9 Nov 2018 04:20:36 +0000 (06:20 +0200)
committerDong Aisheng <aisheng.dong@nxp.com>
Mon, 14 Dec 2020 05:13:31 +0000 (13:13 +0800)
Software controller for IN112525_s03 retimer

Signed-off-by: Florin Chiculita <florinlaurentiu.chiculita@nxp.com>
drivers/net/phy/Kconfig
drivers/net/phy/Makefile
drivers/net/phy/inphi.c [new file with mode: 0644]

index 19f6a0a..bc8cd7d 100644 (file)
@@ -178,6 +178,11 @@ config LXT_PHY
        help
          Currently supports the lxt970, lxt971
 
+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
index a13e402..fcf6dce 100644 (file)
@@ -58,6 +58,7 @@ obj-$(CONFIG_DP83869_PHY)     += dp83869.o
 obj-$(CONFIG_DP83TC811_PHY)    += dp83tc811.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
diff --git a/drivers/net/phy/inphi.c b/drivers/net/phy/inphi.c
new file mode 100644 (file)
index 0000000..cb5370e
--- /dev/null
@@ -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 <linux/module.h>
+#include <linux/phy.h>
+#include <linux/mdio.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/of_irq.h>
+#include <linux/workqueue.h>
+#include <linux/i2c.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#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);