[U-Boot] [PATCH 4/7] phy: add support for AM654x SERDES

Sekhar Nori nsekhar at ti.com
Wed May 15 14:15:02 UTC 2019


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 at ti.com>
---
 drivers/phy/Kconfig        |   9 +
 drivers/phy/Makefile       |   1 +
 drivers/phy/phy-ti-am654.c | 411 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 421 insertions(+)
 create mode 100644 drivers/phy/phy-ti-am654.c

diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 957efb3984bf..cce98448a4b5 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -102,6 +102,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
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index 90646ca55b9d..39da13c4c941 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -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
index 000000000000..39490124eabf
--- /dev/null
+++ b/drivers/phy/phy-ti-am654.c
@@ -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 at 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),
+};
-- 
2.16.2



More information about the U-Boot mailing list