]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
phy: add support for AM654x SERDES
authorSekhar Nori <nsekhar@ti.com>
Thu, 1 Aug 2019 13:42:58 +0000 (19:12 +0530)
committerTom Rini <trini@konsulko.com>
Mon, 12 Aug 2019 17:33:39 +0000 (13:33 -0400)
Add a new SERDES driver for TI's AM654x SoC which configures
the SERDES only for PCIe. Support fo USB3 can be added later.

SERDES in am654x has three input clocks (left input, external
reference clock and right input) and two output clocks (left
output and right output) in addition to a PLL mux clock which
the SERDES uses for Clock Multiplier Unit (CMU refclock).

The PLL mux clock can select from one of the three input
clocks. The right output can select between left input and
external reference clock while the left output can select
between the right input and external reference clock.

The driver has support to select PLL mux and left/right output
mux as specified in device tree.

Signed-off-by: Sekhar Nori <nsekhar@ti.com>
drivers/phy/Kconfig
drivers/phy/Makefile
drivers/phy/phy-ti-am654.c [new file with mode: 0644]

index 8209ca7323d3c6574917d83ba86823864e92117b..3942f035ebb5117775cba9b3bea719de17d00d4d 100644 (file)
@@ -108,6 +108,15 @@ config SPL_PIPE3_PHY
          This PHY is found on omap devices supporting SATA such as dra7, am57x
          and omap5
 
+config AM654_PHY
+       tristate "TI AM654 SERDES support"
+       depends on PHY && ARCH_K3
+       select REGMAP
+       select SYSCON
+       help
+         This option enables support for TI AM654 SerDes PHY used for
+         PCIe.
+
 config STI_USB_PHY
        bool "STMicroelectronics USB2 picoPHY driver for STiH407 family"
        depends on PHY && ARCH_STI
index b9f5195e1cad11cec3c513b7d623d725ac98d5b5..3157f1b7ee58144caaac131fae0b539f848e01e1 100644 (file)
@@ -11,6 +11,7 @@ obj-$(CONFIG_BCM6358_USBH_PHY) += bcm6358-usbh-phy.o
 obj-$(CONFIG_BCM6368_USBH_PHY) += bcm6368-usbh-phy.o
 obj-$(CONFIG_PHY_SANDBOX) += sandbox-phy.o
 obj-$(CONFIG_$(SPL_)PIPE3_PHY) += ti-pipe3-phy.o
+obj-$(CONFIG_AM654_PHY) += phy-ti-am654.o
 obj-$(CONFIG_STI_USB_PHY) += sti_usb_phy.o
 obj-$(CONFIG_PHY_RCAR_GEN2) += phy-rcar-gen2.o
 obj-$(CONFIG_PHY_RCAR_GEN3) += phy-rcar-gen3.o
diff --git a/drivers/phy/phy-ti-am654.c b/drivers/phy/phy-ti-am654.c
new file mode 100644 (file)
index 0000000..3949012
--- /dev/null
@@ -0,0 +1,411 @@
+// SPDX-License-Identifier: GPL-2.0+
+/**
+ * PCIe SERDES driver for AM654x SoC
+ *
+ * Copyright (C) 2018 Texas Instruments
+ * Author: Kishon Vijay Abraham I <kishon@ti.com>
+ */
+
+#include <common.h>
+#include <clk-uclass.h>
+#include <dm.h>
+#include <dm/device.h>
+#include <dm/lists.h>
+#include <dt-bindings/phy/phy.h>
+#include <generic-phy.h>
+#include <asm/io.h>
+#include <asm/arch/sys_proto.h>
+#include <power-domain.h>
+#include <regmap.h>
+#include <syscon.h>
+
+#define CMU_R07C               0x7c
+#define CMU_MASTER_CDN_O       BIT(24)
+
+#define COMLANE_R138           0xb38
+#define CONFIG_VERSION_REG_MASK        GENMASK(23, 16)
+#define CONFIG_VERSION_REG_SHIFT 16
+#define VERSION                        0x70
+
+#define COMLANE_R190           0xb90
+#define L1_MASTER_CDN_O                BIT(9)
+
+#define COMLANE_R194           0xb94
+#define CMU_OK_I_0             BIT(19)
+
+#define SERDES_CTRL            0x1fd0
+#define POR_EN                 BIT(29)
+
+#define WIZ_LANEXCTL_STS       0x1fe0
+#define TX0_ENABLE_OVL         BIT(31)
+#define TX0_ENABLE_MASK                GENMASK(30, 29)
+#define TX0_ENABLE_SHIFT       29
+#define TX0_DISABLE_STATE      0x0
+#define TX0_SLEEP_STATE                0x1
+#define TX0_SNOOZE_STATE       0x2
+#define TX0_ENABLE_STATE       0x3
+#define RX0_ENABLE_OVL         BIT(15)
+#define RX0_ENABLE_MASK                GENMASK(14, 13)
+#define RX0_ENABLE_SHIFT       13
+#define RX0_DISABLE_STATE      0x0
+#define RX0_SLEEP_STATE                0x1
+#define RX0_SNOOZE_STATE       0x2
+#define RX0_ENABLE_STATE       0x3
+
+#define WIZ_PLL_CTRL           0x1ff4
+#define PLL_ENABLE_OVL         BIT(31)
+#define PLL_ENABLE_MASK                GENMASK(30, 29)
+#define PLL_ENABLE_SHIFT       29
+#define PLL_DISABLE_STATE      0x0
+#define PLL_SLEEP_STATE                0x1
+#define PLL_SNOOZE_STATE       0x2
+#define PLL_ENABLE_STATE       0x3
+#define PLL_OK                 BIT(28)
+
+#define PLL_LOCK_TIME          1000    /* in milliseconds */
+#define SLEEP_TIME             100     /* in microseconds */
+
+#define LANE_USB3              0x0
+#define LANE_PCIE0_LANE0       0x1
+
+#define LANE_PCIE1_LANE0       0x0
+#define LANE_PCIE0_LANE1       0x1
+
+#define SERDES_NUM_CLOCKS      3
+
+/* SERDES control MMR bit offsets */
+#define SERDES_CTL_LANE_FUNC_SEL_SHIFT 0
+#define SERDES_CTL_LANE_FUNC_SEL_MASK  GENMASK(1, 0)
+#define SERDES_CTL_CLK_SEL_SHIFT       4
+#define SERDES_CTL_CLK_SEL_MASK                GENMASK(7, 4)
+
+/**
+ * struct serdes_am654_mux_clk_data - clock controller information structure
+ */
+struct serdes_am654_mux_clk_data {
+       struct regmap *regmap;
+       struct clk_bulk parents;
+};
+
+static int serdes_am654_mux_clk_probe(struct udevice *dev)
+{
+       struct serdes_am654_mux_clk_data *data = dev_get_priv(dev);
+       struct udevice *syscon;
+       struct regmap *regmap;
+       int ret;
+
+       debug("%s(dev=%s)\n", __func__, dev->name);
+
+       if (!data)
+               return -ENOMEM;
+
+       ret = uclass_get_device_by_phandle(UCLASS_SYSCON, dev,
+                                          "ti,serdes-clk", &syscon);
+       if (ret) {
+               dev_err(dev, "unable to find syscon device\n");
+               return ret;
+       }
+
+       regmap = syscon_get_regmap(syscon);
+       if (IS_ERR(regmap)) {
+               dev_err(dev, "Fail to get Syscon regmap\n");
+               return PTR_ERR(regmap);
+       }
+
+       data->regmap = regmap;
+
+       ret = clk_get_bulk(dev, &data->parents);
+       if (ret) {
+               dev_err(dev, "Failed to obtain parent clocks\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static int mux_table[SERDES_NUM_CLOCKS][3] = {
+       /*
+        * The entries represent values for selecting between
+        * {left input, external reference clock, right input}
+        * Only one of Left Output or Right Output should be used since
+        * both left and right output clock uses the same bits and modifying
+        * one clock will impact the other.
+        */
+       { BIT(2),               0, BIT(0) }, /* Mux of CMU refclk */
+       {     -1,          BIT(3), BIT(1) }, /* Mux of Left Output */
+       { BIT(1), BIT(3) | BIT(1),     -1 }, /* Mux of Right Output */
+};
+
+static int serdes_am654_mux_clk_set_parent(struct clk *clk, struct clk *parent)
+{
+       struct serdes_am654_mux_clk_data *data = dev_get_priv(clk->dev);
+       u32 val;
+       int i;
+
+       debug("%s(clk=%s, parent=%s)\n", __func__, clk->dev->name,
+             parent->dev->name);
+
+       /*
+        * Since we have the same device-tree node represent both the
+        * clock and serdes device, we have two devices associated with
+        * the serdes node. assigned-clocks for this node is processed twice,
+        * once for the clock device and another time for the serdes
+        * device. When it is processed for the clock device, it is before
+        * the probe for clock device has been called. We ignore this case
+        * and rely on assigned-clocks to be processed correctly for the
+        * serdes case.
+        */
+       if (!data->regmap)
+               return 0;
+
+       for (i = 0; i < data->parents.count; i++) {
+               if (clk_is_match(&data->parents.clks[i], parent))
+                       break;
+       }
+
+       if (i >= data->parents.count)
+               return -EINVAL;
+
+       val = mux_table[clk->id][i];
+       val <<= SERDES_CTL_CLK_SEL_SHIFT;
+
+       regmap_update_bits(data->regmap, 0, SERDES_CTL_CLK_SEL_MASK, val);
+
+       return 0;
+}
+
+static struct clk_ops serdes_am654_mux_clk_ops = {
+       .set_parent = serdes_am654_mux_clk_set_parent,
+};
+
+U_BOOT_DRIVER(serdes_am654_mux_clk) = {
+       .name = "ti-serdes-am654-mux-clk",
+       .id = UCLASS_CLK,
+       .probe = serdes_am654_mux_clk_probe,
+       .priv_auto_alloc_size = sizeof(struct serdes_am654_mux_clk_data),
+       .ops = &serdes_am654_mux_clk_ops,
+};
+
+struct serdes_am654 {
+       struct regmap *regmap;
+       struct regmap *serdes_ctl;
+};
+
+static int serdes_am654_enable_pll(struct serdes_am654 *phy)
+{
+       u32 mask = PLL_ENABLE_OVL | PLL_ENABLE_MASK;
+       u32 val = PLL_ENABLE_OVL | (PLL_ENABLE_STATE << PLL_ENABLE_SHIFT);
+
+       regmap_update_bits(phy->regmap, WIZ_PLL_CTRL, mask, val);
+
+       return regmap_read_poll_timeout(phy->regmap, WIZ_PLL_CTRL, val,
+                                       val & PLL_OK, 1000, PLL_LOCK_TIME);
+}
+
+static void serdes_am654_disable_pll(struct serdes_am654 *phy)
+{
+       u32 mask = PLL_ENABLE_OVL | PLL_ENABLE_MASK;
+
+       regmap_update_bits(phy->regmap, WIZ_PLL_CTRL, mask, 0);
+}
+
+static int serdes_am654_enable_txrx(struct serdes_am654 *phy)
+{
+       u32 mask;
+       u32 val;
+
+       /* Enable TX */
+       mask = TX0_ENABLE_OVL | TX0_ENABLE_MASK;
+       val = TX0_ENABLE_OVL | (TX0_ENABLE_STATE << TX0_ENABLE_SHIFT);
+       regmap_update_bits(phy->regmap, WIZ_LANEXCTL_STS, mask, val);
+
+       /* Enable RX */
+       mask = RX0_ENABLE_OVL | RX0_ENABLE_MASK;
+       val = RX0_ENABLE_OVL | (RX0_ENABLE_STATE << RX0_ENABLE_SHIFT);
+       regmap_update_bits(phy->regmap, WIZ_LANEXCTL_STS, mask, val);
+
+       return 0;
+}
+
+static int serdes_am654_disable_txrx(struct serdes_am654 *phy)
+{
+       u32 mask;
+
+       /* Disable TX */
+       mask = TX0_ENABLE_OVL | TX0_ENABLE_MASK;
+       regmap_update_bits(phy->regmap, WIZ_LANEXCTL_STS, mask, 0);
+
+       /* Disable RX */
+       mask = RX0_ENABLE_OVL | RX0_ENABLE_MASK;
+       regmap_update_bits(phy->regmap, WIZ_LANEXCTL_STS, mask, 0);
+
+       return 0;
+}
+
+static int serdes_am654_power_on(struct phy *x)
+{
+       struct serdes_am654 *phy = dev_get_priv(x->dev);
+       int ret;
+       u32 val;
+
+       ret = serdes_am654_enable_pll(phy);
+       if (ret) {
+               dev_err(x->dev, "Failed to enable PLL\n");
+               return ret;
+       }
+
+       ret = serdes_am654_enable_txrx(phy);
+       if (ret) {
+               dev_err(x->dev, "Failed to enable TX RX\n");
+               return ret;
+       }
+
+       return regmap_read_poll_timeout(phy->regmap, COMLANE_R194, val,
+                                       val & CMU_OK_I_0, SLEEP_TIME,
+                                       PLL_LOCK_TIME);
+}
+
+static int serdes_am654_power_off(struct phy *x)
+{
+       struct serdes_am654 *phy = dev_get_priv(x->dev);
+
+       serdes_am654_disable_txrx(phy);
+       serdes_am654_disable_pll(phy);
+
+       return 0;
+}
+
+static int serdes_am654_init(struct phy *x)
+{
+       struct serdes_am654 *phy = dev_get_priv(x->dev);
+       u32 mask;
+       u32 val;
+
+       mask = CONFIG_VERSION_REG_MASK;
+       val = VERSION << CONFIG_VERSION_REG_SHIFT;
+       regmap_update_bits(phy->regmap, COMLANE_R138, mask, val);
+
+       val = CMU_MASTER_CDN_O;
+       regmap_update_bits(phy->regmap, CMU_R07C, val, val);
+
+       val = L1_MASTER_CDN_O;
+       regmap_update_bits(phy->regmap, COMLANE_R190, val, val);
+
+       return 0;
+}
+
+static int serdes_am654_reset(struct phy *x)
+{
+       struct serdes_am654 *phy = dev_get_priv(x->dev);
+       u32 val;
+
+       val = POR_EN;
+       regmap_update_bits(phy->regmap, SERDES_CTRL, val, val);
+       mdelay(1);
+       regmap_update_bits(phy->regmap, SERDES_CTRL, val, 0);
+
+       return 0;
+}
+
+static int serdes_am654_of_xlate(struct phy *x,
+                                struct ofnode_phandle_args *args)
+{
+       struct serdes_am654 *phy = dev_get_priv(x->dev);
+
+       if (args->args_count != 2) {
+               dev_err(phy->dev, "Invalid DT PHY argument count: %d\n",
+                       args->args_count);
+               return -EINVAL;
+       }
+
+       if (args->args[0] != PHY_TYPE_PCIE) {
+               dev_err(phy->dev, "Unrecognized PHY type: %d\n",
+                       args->args[0]);
+               return -EINVAL;
+       }
+
+       x->id = args->args[0] | (args->args[1] << 16);
+
+       /* Setup mux mode using second argument */
+       regmap_update_bits(phy->serdes_ctl, 0, SERDES_CTL_LANE_FUNC_SEL_MASK,
+                          args->args[1]);
+
+       return 0;
+}
+
+static int serdes_am654_bind(struct udevice *dev)
+{
+       int ret;
+
+       ret = device_bind_driver_to_node(dev->parent,
+                                        "ti-serdes-am654-mux-clk",
+                                        dev_read_name(dev), dev->node,
+                                        NULL);
+       if (ret) {
+               dev_err(dev, "%s: not able to bind clock driver\n", __func__);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int serdes_am654_probe(struct udevice *dev)
+{
+       struct serdes_am654 *phy = dev_get_priv(dev);
+       struct power_domain serdes_pwrdmn;
+       struct regmap *serdes_ctl;
+       struct regmap *map;
+       int ret;
+
+       ret = regmap_init_mem(dev_ofnode(dev), &map);
+       if (ret)
+               return ret;
+
+       phy->regmap = map;
+
+       serdes_ctl = syscon_regmap_lookup_by_phandle(dev, "ti,serdes-clk");
+       if (IS_ERR(serdes_ctl)) {
+               dev_err(dev, "unable to find syscon device\n");
+               return PTR_ERR(serdes_ctl);
+       }
+
+       phy->serdes_ctl = serdes_ctl;
+
+       ret = power_domain_get_by_index(dev, &serdes_pwrdmn, 0);
+       if (ret) {
+               dev_err(dev, "failed to get power domain\n");
+               return ret;
+       }
+
+       ret = power_domain_on(&serdes_pwrdmn);
+       if (ret) {
+               dev_err(dev, "Power domain on failed\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static const struct udevice_id serdes_am654_phy_ids[] = {
+       {
+               .compatible = "ti,phy-am654-serdes",
+       },
+};
+
+static const struct phy_ops serdes_am654_phy_ops = {
+       .reset          = serdes_am654_reset,
+       .init           = serdes_am654_init,
+       .power_on       = serdes_am654_power_on,
+       .power_off      = serdes_am654_power_off,
+       .of_xlate       = serdes_am654_of_xlate,
+};
+
+U_BOOT_DRIVER(am654_serdes_phy) = {
+       .name   = "am654_serdes_phy",
+       .id     = UCLASS_PHY,
+       .of_match = serdes_am654_phy_ids,
+       .bind = serdes_am654_bind,
+       .ops = &serdes_am654_phy_ops,
+       .probe = serdes_am654_probe,
+       .priv_auto_alloc_size = sizeof(struct serdes_am654),
+};