phy: add driver for Microsemi Ocelot SerDes muxing
authorQuentin Schulz <quentin.schulz@bootlin.com>
Thu, 4 Oct 2018 12:22:07 +0000 (14:22 +0200)
committerDavid S. Miller <davem@davemloft.net>
Fri, 5 Oct 2018 21:36:44 +0000 (14:36 -0700)
The Microsemi Ocelot can mux SerDes lanes (aka macros) to different
switch ports or even make it act as a PCIe interface.

This adds support for the muxing of the SerDes.

Signed-off-by: Quentin Schulz <quentin.schulz@bootlin.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/phy/Kconfig
drivers/phy/Makefile
drivers/phy/mscc/Kconfig [new file with mode: 0644]
drivers/phy/mscc/Makefile [new file with mode: 0644]
drivers/phy/mscc/phy-ocelot-serdes.c [new file with mode: 0644]

index 5c8d452..c89d3ef 100644 (file)
@@ -48,6 +48,7 @@ source "drivers/phy/lantiq/Kconfig"
 source "drivers/phy/marvell/Kconfig"
 source "drivers/phy/mediatek/Kconfig"
 source "drivers/phy/motorola/Kconfig"
+source "drivers/phy/mscc/Kconfig"
 source "drivers/phy/qualcomm/Kconfig"
 source "drivers/phy/ralink/Kconfig"
 source "drivers/phy/renesas/Kconfig"
index 84e3bd9..ce8339f 100644 (file)
@@ -18,6 +18,7 @@ obj-y                                 += broadcom/    \
                                           hisilicon/   \
                                           marvell/     \
                                           motorola/    \
+                                          mscc/        \
                                           qualcomm/    \
                                           ralink/      \
                                           samsung/     \
diff --git a/drivers/phy/mscc/Kconfig b/drivers/phy/mscc/Kconfig
new file mode 100644 (file)
index 0000000..2e2a466
--- /dev/null
@@ -0,0 +1,11 @@
+#
+# Phy drivers for Microsemi devices
+#
+
+config PHY_OCELOT_SERDES
+       tristate "SerDes PHY driver for Microsemi Ocelot"
+       select GENERIC_PHY
+       depends on OF
+       depends on MFD_SYSCON
+       help
+         Enable this for supporting SerDes muxing with Microsemi Ocelot.
diff --git a/drivers/phy/mscc/Makefile b/drivers/phy/mscc/Makefile
new file mode 100644 (file)
index 0000000..e147491
--- /dev/null
@@ -0,0 +1,5 @@
+#
+# Makefile for the Microsemi phy drivers.
+#
+
+obj-$(CONFIG_PHY_OCELOT_SERDES) := phy-ocelot-serdes.o
diff --git a/drivers/phy/mscc/phy-ocelot-serdes.c b/drivers/phy/mscc/phy-ocelot-serdes.c
new file mode 100644 (file)
index 0000000..8936abd
--- /dev/null
@@ -0,0 +1,295 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * SerDes PHY driver for Microsemi Ocelot
+ *
+ * Copyright (c) 2018 Microsemi
+ *
+ */
+
+#include <linux/err.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <soc/mscc/ocelot_hsio.h>
+#include <dt-bindings/phy/phy-ocelot-serdes.h>
+
+struct serdes_ctrl {
+       struct regmap           *regs;
+       struct device           *dev;
+       struct phy              *phys[SERDES_MAX];
+};
+
+struct serdes_macro {
+       u8                      idx;
+       /* Not used when in QSGMII or PCIe mode */
+       int                     port;
+       struct serdes_ctrl      *ctrl;
+};
+
+#define MCB_S1G_CFG_TIMEOUT     50
+
+static int __serdes_write_mcb_s1g(struct regmap *regmap, u8 macro, u32 op)
+{
+       unsigned int regval;
+
+       regmap_write(regmap, HSIO_MCB_S1G_ADDR_CFG, op |
+                    HSIO_MCB_S1G_ADDR_CFG_SERDES1G_ADDR(BIT(macro)));
+
+       return regmap_read_poll_timeout(regmap, HSIO_MCB_S1G_ADDR_CFG, regval,
+                                       (regval & op) != op, 100,
+                                       MCB_S1G_CFG_TIMEOUT * 1000);
+}
+
+static int serdes_commit_mcb_s1g(struct regmap *regmap, u8 macro)
+{
+       return __serdes_write_mcb_s1g(regmap, macro,
+               HSIO_MCB_S1G_ADDR_CFG_SERDES1G_WR_ONE_SHOT);
+}
+
+static int serdes_update_mcb_s1g(struct regmap *regmap, u8 macro)
+{
+       return __serdes_write_mcb_s1g(regmap, macro,
+               HSIO_MCB_S1G_ADDR_CFG_SERDES1G_RD_ONE_SHOT);
+}
+
+static int serdes_init_s1g(struct regmap *regmap, u8 serdes)
+{
+       int ret;
+
+       ret = serdes_update_mcb_s1g(regmap, serdes);
+       if (ret)
+               return ret;
+
+       regmap_update_bits(regmap, HSIO_S1G_COMMON_CFG,
+                          HSIO_S1G_COMMON_CFG_SYS_RST |
+                          HSIO_S1G_COMMON_CFG_ENA_LANE |
+                          HSIO_S1G_COMMON_CFG_ENA_ELOOP |
+                          HSIO_S1G_COMMON_CFG_ENA_FLOOP,
+                          HSIO_S1G_COMMON_CFG_ENA_LANE);
+
+       regmap_update_bits(regmap, HSIO_S1G_PLL_CFG,
+                          HSIO_S1G_PLL_CFG_PLL_FSM_ENA |
+                          HSIO_S1G_PLL_CFG_PLL_FSM_CTRL_DATA_M,
+                          HSIO_S1G_PLL_CFG_PLL_FSM_CTRL_DATA(200) |
+                          HSIO_S1G_PLL_CFG_PLL_FSM_ENA);
+
+       regmap_update_bits(regmap, HSIO_S1G_MISC_CFG,
+                          HSIO_S1G_MISC_CFG_DES_100FX_CPMD_ENA |
+                          HSIO_S1G_MISC_CFG_LANE_RST,
+                          HSIO_S1G_MISC_CFG_LANE_RST);
+
+       ret = serdes_commit_mcb_s1g(regmap, serdes);
+       if (ret)
+               return ret;
+
+       regmap_update_bits(regmap, HSIO_S1G_COMMON_CFG,
+                          HSIO_S1G_COMMON_CFG_SYS_RST,
+                          HSIO_S1G_COMMON_CFG_SYS_RST);
+
+       regmap_update_bits(regmap, HSIO_S1G_MISC_CFG,
+                          HSIO_S1G_MISC_CFG_LANE_RST, 0);
+
+       ret = serdes_commit_mcb_s1g(regmap, serdes);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+struct serdes_mux {
+       u8                      idx;
+       u8                      port;
+       enum phy_mode           mode;
+       u32                     mask;
+       u32                     mux;
+};
+
+#define SERDES_MUX(_idx, _port, _mode, _mask, _mux) {          \
+       .idx = _idx,                                            \
+       .port = _port,                                          \
+       .mode = _mode,                                          \
+       .mask = _mask,                                          \
+       .mux = _mux,                                            \
+}
+
+#define SERDES_MUX_SGMII(i, p, m, c) SERDES_MUX(i, p, PHY_MODE_SGMII, m, c)
+#define SERDES_MUX_QSGMII(i, p, m, c) SERDES_MUX(i, p, PHY_MODE_QSGMII, m, c)
+
+static const struct serdes_mux ocelot_serdes_muxes[] = {
+       SERDES_MUX_SGMII(SERDES1G(0), 0, 0, 0),
+       SERDES_MUX_SGMII(SERDES1G(1), 1, HSIO_HW_CFG_DEV1G_5_MODE, 0),
+       SERDES_MUX_SGMII(SERDES1G(1), 5, HSIO_HW_CFG_QSGMII_ENA |
+                        HSIO_HW_CFG_DEV1G_5_MODE, HSIO_HW_CFG_DEV1G_5_MODE),
+       SERDES_MUX_SGMII(SERDES1G(2), 2, HSIO_HW_CFG_DEV1G_4_MODE, 0),
+       SERDES_MUX_SGMII(SERDES1G(2), 4, HSIO_HW_CFG_QSGMII_ENA |
+                        HSIO_HW_CFG_DEV1G_4_MODE, HSIO_HW_CFG_DEV1G_4_MODE),
+       SERDES_MUX_SGMII(SERDES1G(3), 3, HSIO_HW_CFG_DEV1G_6_MODE, 0),
+       SERDES_MUX_SGMII(SERDES1G(3), 6, HSIO_HW_CFG_QSGMII_ENA |
+                        HSIO_HW_CFG_DEV1G_6_MODE, HSIO_HW_CFG_DEV1G_6_MODE),
+       SERDES_MUX_SGMII(SERDES1G(4), 4, HSIO_HW_CFG_QSGMII_ENA |
+                        HSIO_HW_CFG_DEV1G_4_MODE | HSIO_HW_CFG_DEV1G_9_MODE,
+                        0),
+       SERDES_MUX_SGMII(SERDES1G(4), 9, HSIO_HW_CFG_DEV1G_4_MODE |
+                        HSIO_HW_CFG_DEV1G_9_MODE, HSIO_HW_CFG_DEV1G_4_MODE |
+                        HSIO_HW_CFG_DEV1G_9_MODE),
+       SERDES_MUX_SGMII(SERDES1G(5), 5, HSIO_HW_CFG_QSGMII_ENA |
+                        HSIO_HW_CFG_DEV1G_5_MODE | HSIO_HW_CFG_DEV2G5_10_MODE,
+                        0),
+       SERDES_MUX_SGMII(SERDES1G(5), 10, HSIO_HW_CFG_PCIE_ENA |
+                        HSIO_HW_CFG_DEV1G_5_MODE | HSIO_HW_CFG_DEV2G5_10_MODE,
+                        HSIO_HW_CFG_DEV1G_5_MODE | HSIO_HW_CFG_DEV2G5_10_MODE),
+       SERDES_MUX_QSGMII(SERDES6G(0), 4, HSIO_HW_CFG_QSGMII_ENA,
+                         HSIO_HW_CFG_QSGMII_ENA),
+       SERDES_MUX_QSGMII(SERDES6G(0), 5, HSIO_HW_CFG_QSGMII_ENA,
+                         HSIO_HW_CFG_QSGMII_ENA),
+       SERDES_MUX_QSGMII(SERDES6G(0), 6, HSIO_HW_CFG_QSGMII_ENA,
+                         HSIO_HW_CFG_QSGMII_ENA),
+       SERDES_MUX_SGMII(SERDES6G(0), 7, HSIO_HW_CFG_QSGMII_ENA, 0),
+       SERDES_MUX_QSGMII(SERDES6G(0), 7, HSIO_HW_CFG_QSGMII_ENA,
+                         HSIO_HW_CFG_QSGMII_ENA),
+       SERDES_MUX_SGMII(SERDES6G(1), 8, 0, 0),
+       SERDES_MUX_SGMII(SERDES6G(2), 10, HSIO_HW_CFG_PCIE_ENA |
+                        HSIO_HW_CFG_DEV2G5_10_MODE, 0),
+       SERDES_MUX(SERDES6G(2), 10, PHY_MODE_PCIE, HSIO_HW_CFG_PCIE_ENA,
+                  HSIO_HW_CFG_PCIE_ENA),
+};
+
+static int serdes_set_mode(struct phy *phy, enum phy_mode mode)
+{
+       struct serdes_macro *macro = phy_get_drvdata(phy);
+       unsigned int i;
+       int ret;
+
+       for (i = 0; i < ARRAY_SIZE(ocelot_serdes_muxes); i++) {
+               if (macro->idx != ocelot_serdes_muxes[i].idx ||
+                   mode != ocelot_serdes_muxes[i].mode)
+                       continue;
+
+               if (mode != PHY_MODE_QSGMII &&
+                   macro->port != ocelot_serdes_muxes[i].port)
+                       continue;
+
+               ret = regmap_update_bits(macro->ctrl->regs, HSIO_HW_CFG,
+                                        ocelot_serdes_muxes[i].mask,
+                                        ocelot_serdes_muxes[i].mux);
+               if (ret)
+                       return ret;
+
+               if (macro->idx <= SERDES1G_MAX)
+                       return serdes_init_s1g(macro->ctrl->regs, macro->idx);
+
+               /* SERDES6G and PCIe not supported yet */
+               return -EOPNOTSUPP;
+       }
+
+       return -EINVAL;
+}
+
+static const struct phy_ops serdes_ops = {
+       .set_mode       = serdes_set_mode,
+       .owner          = THIS_MODULE,
+};
+
+static struct phy *serdes_simple_xlate(struct device *dev,
+                                      struct of_phandle_args *args)
+{
+       struct serdes_ctrl *ctrl = dev_get_drvdata(dev);
+       unsigned int port, idx, i;
+
+       if (args->args_count != 2)
+               return ERR_PTR(-EINVAL);
+
+       port = args->args[0];
+       idx = args->args[1];
+
+       for (i = 0; i <= SERDES_MAX; i++) {
+               struct serdes_macro *macro = phy_get_drvdata(ctrl->phys[i]);
+
+               if (idx != macro->idx)
+                       continue;
+
+               /* SERDES6G(0) is the only SerDes capable of QSGMII */
+               if (idx != SERDES6G(0) && macro->port >= 0)
+                       return ERR_PTR(-EBUSY);
+
+               macro->port = port;
+               return ctrl->phys[i];
+       }
+
+       return ERR_PTR(-ENODEV);
+}
+
+static int serdes_phy_create(struct serdes_ctrl *ctrl, u8 idx, struct phy **phy)
+{
+       struct serdes_macro *macro;
+
+       *phy = devm_phy_create(ctrl->dev, NULL, &serdes_ops);
+       if (IS_ERR(*phy))
+               return PTR_ERR(*phy);
+
+       macro = devm_kzalloc(ctrl->dev, sizeof(*macro), GFP_KERNEL);
+       if (!macro)
+               return -ENOMEM;
+
+       macro->idx = idx;
+       macro->ctrl = ctrl;
+       macro->port = -1;
+
+       phy_set_drvdata(*phy, macro);
+
+       return 0;
+}
+
+static int serdes_probe(struct platform_device *pdev)
+{
+       struct phy_provider *provider;
+       struct serdes_ctrl *ctrl;
+       unsigned int i;
+       int ret;
+
+       ctrl = devm_kzalloc(&pdev->dev, sizeof(*ctrl), GFP_KERNEL);
+       if (!ctrl)
+               return -ENOMEM;
+
+       ctrl->dev = &pdev->dev;
+       ctrl->regs = syscon_node_to_regmap(pdev->dev.parent->of_node);
+       if (!ctrl->regs)
+               return -ENODEV;
+
+       for (i = 0; i <= SERDES_MAX; i++) {
+               ret = serdes_phy_create(ctrl, i, &ctrl->phys[i]);
+               if (ret)
+                       return ret;
+       }
+
+       dev_set_drvdata(&pdev->dev, ctrl);
+
+       provider = devm_of_phy_provider_register(ctrl->dev,
+                                                serdes_simple_xlate);
+
+       return PTR_ERR_OR_ZERO(provider);
+}
+
+static const struct of_device_id serdes_ids[] = {
+       { .compatible = "mscc,vsc7514-serdes", },
+       {},
+};
+MODULE_DEVICE_TABLE(of, serdes_ids);
+
+static struct platform_driver mscc_ocelot_serdes = {
+       .probe          = serdes_probe,
+       .driver         = {
+               .name   = "mscc,ocelot-serdes",
+               .of_match_table = of_match_ptr(serdes_ids),
+       },
+};
+
+module_platform_driver(mscc_ocelot_serdes);
+
+MODULE_AUTHOR("Quentin Schulz <quentin.schulz@bootlin.com>");
+MODULE_DESCRIPTION("SerDes driver for Microsemi Ocelot");
+MODULE_LICENSE("Dual MIT/GPL");