[PATCH 12/18] phy: at91: Add support for the USB 2.0 PHY's of SAMA7

Sergiu Moga sergiu.moga at microchip.com
Wed Dec 7 14:14:41 CET 2022


In order to have USB functionality, drivers for SAMA7's
USB 2.0 PHY's have been added. There is one driver
for UTMI clock's SFR and RESET required functionalities and
one for its three possible subclocks of the phy's themselves.
In order for this layout to properly work in conjunction with
CCF and DT, the former driver will also act as a clock provider
for the three phy's with the help of a custom hook into the
driver's of_xlate method.

Signed-off-by: Sergiu Moga <sergiu.moga at microchip.com>
Tested-by: Mihai Sain <mihai.sain at microchip.com>
---
 drivers/phy/Kconfig              |  10 ++
 drivers/phy/Makefile             |   1 +
 drivers/phy/phy-sama7-usb.c      |  92 ++++++++++++++
 drivers/phy/phy-sama7-utmi-clk.c | 202 +++++++++++++++++++++++++++++++
 4 files changed, 305 insertions(+)
 create mode 100644 drivers/phy/phy-sama7-usb.c
 create mode 100644 drivers/phy/phy-sama7-utmi-clk.c

diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index cf4d5908d7..9fbb956783 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -281,6 +281,16 @@ config PHY_XILINX_ZYNQMP
 	  Enable this to support ZynqMP High Speed Gigabit Transceiver
 	  that is part of ZynqMP SoC.
 
+config PHY_MICROCHIP_SAMA7_USB
+       tristate "Microchip SAMA7 USB 2.0 PHY"
+       depends on PHY && ARCH_AT91
+       help
+	 Enable this to support SAMA7 USB 2.0 PHY.
+
+	 The USB 2.0 PHY integrates high-speed, full-speed and low-speed
+	 termination and signal switching. With a single resistor, it
+	 requires minimal external components.
+
 source "drivers/phy/rockchip/Kconfig"
 source "drivers/phy/cadence/Kconfig"
 source "drivers/phy/ti/Kconfig"
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index a3b9f3c5b1..9d50affd47 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -38,6 +38,7 @@ obj-$(CONFIG_PHY_MTK_TPHY) += phy-mtk-tphy.o
 obj-$(CONFIG_PHY_NPCM_USB) += phy-npcm-usb.o
 obj-$(CONFIG_PHY_IMX8MQ_USB) += phy-imx8mq-usb.o
 obj-$(CONFIG_PHY_XILINX_ZYNQMP) += phy-zynqmp.o
+obj-$(CONFIG_PHY_MICROCHIP_SAMA7_USB)  += phy-sama7-utmi-clk.o phy-sama7-usb.o
 obj-y += cadence/
 obj-y += ti/
 obj-y += qcom/
diff --git a/drivers/phy/phy-sama7-usb.c b/drivers/phy/phy-sama7-usb.c
new file mode 100644
index 0000000000..b6fe40ecc1
--- /dev/null
+++ b/drivers/phy/phy-sama7-usb.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Support for Atmel/Microchip USB PHY's.
+ *
+ * Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Sergiu Moga <sergiu.moga at microchip.com>
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <generic-phy.h>
+#include <syscon.h>
+#include <regmap.h>
+#include <mach/sama7-sfr.h>
+
+struct sama7_usb_phy {
+	struct clk *uclk;
+	struct regmap *sfr;
+	int port;
+};
+
+int sama7_usb_phy_init(struct phy *phy)
+{
+	struct sama7_usb_phy *sama7_phy = dev_get_priv(phy->dev);
+	int port = sama7_phy->port;
+
+	regmap_update_bits(sama7_phy->sfr, SAMA7_SFR_UTMI0R(port),
+			   SAMA7_SFR_UTMI_RX_TX_PREEM_AMP_TUNE_1X,
+			   SAMA7_SFR_UTMI_RX_TX_PREEM_AMP_TUNE_1X);
+
+	regmap_update_bits(sama7_phy->sfr, SAMA7_SFR_UTMI0R(port),
+			   SAMA7_SFR_UTMI_RX_VBUS,
+			   SAMA7_SFR_UTMI_RX_VBUS);
+
+	return 0;
+}
+
+int sama7_phy_power_on(struct phy *phy)
+{
+	struct sama7_usb_phy *sama7_phy = dev_get_priv(phy->dev);
+
+	clk_prepare_enable(sama7_phy->uclk);
+
+	return 0;
+}
+
+int sama7_phy_power_off(struct phy *phy)
+{
+	struct sama7_usb_phy *sama7_phy = dev_get_priv(phy->dev);
+
+	clk_disable_unprepare(sama7_phy->uclk);
+
+	return 0;
+}
+
+int sama7_usb_phy_probe(struct udevice *dev)
+{
+	struct sama7_usb_phy *sama7_phy = dev_get_priv(dev);
+
+	sama7_phy->uclk = devm_clk_get(dev, "utmi_clk");
+	if (IS_ERR(sama7_phy->uclk))
+		return PTR_ERR(sama7_phy->uclk);
+
+	sama7_phy->sfr = syscon_regmap_lookup_by_phandle(dev, "sfr-phandle");
+	if (IS_ERR(sama7_phy->sfr)) {
+		sama7_phy->sfr = NULL;
+		return PTR_ERR(sama7_phy->sfr);
+	}
+
+	return dev_read_u32(dev, "reg", &sama7_phy->port);
+}
+
+static const struct phy_ops sama7_usb_phy_ops = {
+	.init = sama7_usb_phy_init,
+	.power_on = sama7_phy_power_on,
+	.power_off = sama7_phy_power_off,
+};
+
+static const struct udevice_id sama7_usb_phy_of_match[] = {
+	{ .compatible = "microchip,sama7g5-usb-phy", },
+	{ },
+};
+
+U_BOOT_DRIVER(sama7_usb_phy_driver) = {
+	.name = "sama7-usb-phy",
+	.id = UCLASS_PHY,
+	.of_match = sama7_usb_phy_of_match,
+	.ops = &sama7_usb_phy_ops,
+	.probe = sama7_usb_phy_probe,
+	.priv_auto = sizeof(struct sama7_usb_phy),
+};
diff --git a/drivers/phy/phy-sama7-utmi-clk.c b/drivers/phy/phy-sama7-utmi-clk.c
new file mode 100644
index 0000000000..ab9fddccf6
--- /dev/null
+++ b/drivers/phy/phy-sama7-utmi-clk.c
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Support for Atmel/Microchip USB PHY's.
+ *
+ * Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Sergiu Moga <sergiu.moga at microchip.com>
+ */
+
+#include <dm.h>
+#include <linux/clk-provider.h>
+#include <syscon.h>
+#include <regmap.h>
+#include <mach/sama7-sfr.h>
+#include <reset.h>
+#include <dt-bindings/clk/at91.h>
+
+struct sama7_utmi_clk {
+	struct clk		uclk;
+	struct regmap		*regmap_sfr;
+	struct reset_ctl	*reset;
+	u8 id;
+};
+
+#define to_sama7_utmi_clk(_c) container_of(_c, struct sama7_utmi_clk, uclk)
+
+#define UBOOT_DM_CLK_MICROCHIP_SAMA7G5_UTMI "sama7-utmi-clk"
+#define UBOOT_DM_MICROCHIP_SAMA7G5_UTMI "sama7-utmi"
+
+#define AT91_TO_CLK_ID(_t, _i)		(((_t) << 8) | ((_i) & 0xff))
+
+/*
+ * UTMI clock description
+ * @n:	clock name
+ * @p:	clock parent name
+ * @id: clock id in RSTC_GRSTR
+ */
+static struct {
+	const char *n;
+	const char *p;
+	u8 id;
+} sama7_utmick[] = {
+	{ .n = "utmi1",	.p = "utmick", .id = 0, },
+	{ .n = "utmi2",	.p = "utmi1",  .id = 1, },
+	{ .n = "utmi3",	.p = "utmi1",  .id = 2, },
+};
+
+static int sama7_utmi_clk_enable(struct clk *clk)
+{
+	int ret;
+
+	struct sama7_utmi_clk *utmi = to_sama7_utmi_clk(clk);
+	u8 id = utmi->id;
+
+	ret = reset_assert(utmi->reset);
+	if (ret)
+		return ret;
+
+	ret = regmap_update_bits(utmi->regmap_sfr, SAMA7_SFR_UTMI0R(id),
+				 SAMA7_SFR_UTMI_COMMONON, 0);
+	if (ret < 0)
+		return ret;
+
+	ret = reset_deassert(utmi->reset);
+	if (ret)
+		return ret;
+
+	/* Datasheet states a minimum of 45 us before any USB operation */
+	udelay(50);
+
+	return 0;
+}
+
+static int sama7_utmi_clk_disable(struct clk *clk)
+{
+	int ret;
+	struct sama7_utmi_clk *utmi = to_sama7_utmi_clk(clk);
+	u8 id = utmi->id;
+
+	ret = reset_assert(utmi->reset);
+	if (ret)
+		return ret;
+
+	regmap_update_bits(utmi->regmap_sfr, SAMA7_SFR_UTMI0R(id),
+			   SAMA7_SFR_UTMI_COMMONON, SAMA7_SFR_UTMI_COMMONON);
+
+	return 0;
+}
+
+static ulong sama7_utmi_clk_get_rate(struct clk *clk)
+{
+	/* Return utmick's rate: 480MHz */
+	return clk_get_parent_rate(clk);
+}
+
+static const struct clk_ops sama7_utmi_clk_ops = {
+	.enable = sama7_utmi_clk_enable,
+	.disable = sama7_utmi_clk_disable,
+	.get_rate = sama7_utmi_clk_get_rate,
+};
+
+static struct clk*
+sama7_utmi_clk_register(struct regmap *regmap_sfr, struct reset_ctl *reset,
+			const char *name, const char *parent_name, u8 id)
+{
+	struct clk *clk;
+	struct sama7_utmi_clk *utmi_clk;
+	int ret;
+
+	if (!regmap_sfr || !reset || !name || !parent_name)
+		return ERR_PTR(-EINVAL);
+
+	utmi_clk = kzalloc(sizeof(*utmi_clk), GFP_KERNEL);
+	if (!utmi_clk)
+		return ERR_PTR(-ENOMEM);
+
+	utmi_clk->reset = reset;
+	utmi_clk->regmap_sfr = regmap_sfr;
+	utmi_clk->id = id;
+
+	clk = &utmi_clk->uclk;
+	ret = clk_register(clk, UBOOT_DM_CLK_MICROCHIP_SAMA7G5_UTMI,
+			   name, parent_name);
+	if (ret) {
+		kfree(utmi_clk);
+		clk = ERR_PTR(ret);
+	}
+
+	clk_dm(AT91_TO_CLK_ID(UTMI, utmi_clk->id), clk);
+
+	return clk;
+}
+
+static int sama7_utmi_probe(struct udevice *dev)
+{
+	struct clk *utmi_parent_clk, *utmi_clk;
+	struct regmap *regmap_sfr;
+	struct reset_ctl *phy_reset;
+	int i;
+	char name[16];
+
+	utmi_parent_clk = devm_clk_get(dev, "utmi_clk");
+	if (IS_ERR(utmi_parent_clk))
+		return PTR_ERR(utmi_parent_clk);
+
+	regmap_sfr = syscon_regmap_lookup_by_phandle(dev, "sfr-phandle");
+	if (IS_ERR(regmap_sfr))
+		return PTR_ERR(regmap_sfr);
+
+	for (i = 0; i < ARRAY_SIZE(sama7_utmick); i++) {
+		snprintf(name, sizeof(name), "usb%d_reset", i);
+		phy_reset = devm_reset_control_get(dev, name);
+		if (IS_ERR(phy_reset))
+			return PTR_ERR(phy_reset);
+
+		utmi_clk = sama7_utmi_clk_register(regmap_sfr, phy_reset,
+						   sama7_utmick[i].n,
+						   sama7_utmick[i].p,
+						   sama7_utmick[i].id);
+		if (IS_ERR(utmi_clk))
+			return PTR_ERR(utmi_clk);
+	}
+
+	return 0;
+};
+
+static const struct udevice_id sama7_utmi_clk_dt_ids[] = {
+	{ .compatible = "microchip,sama7g5-utmi-clk", },
+	{ /* sentinel */},
+};
+
+static int utmi_clk_of_xlate(struct clk *clk, struct ofnode_phandle_args *args)
+{
+	if (args->args_count != 1) {
+		debug("UTMI: clk: Invalid args_count: %d\n", args->args_count);
+		return -EINVAL;
+	}
+
+	clk->id = AT91_TO_CLK_ID(UTMI, args->args[0]);
+
+	return 0;
+}
+
+static const struct clk_ops sama7_utmi_ops = {
+	.of_xlate	= utmi_clk_of_xlate,
+	.enable		= ccf_clk_enable,
+	.disable	= ccf_clk_disable,
+};
+
+U_BOOT_DRIVER(microhip_sama7g5_utmi_clk) = {
+	.name = UBOOT_DM_CLK_MICROCHIP_SAMA7G5_UTMI,
+	.id = UCLASS_CLK,
+	.ops = &sama7_utmi_clk_ops,
+};
+
+U_BOOT_DRIVER(microhip_sama7g5_utmi) = {
+	.name = UBOOT_DM_MICROCHIP_SAMA7G5_UTMI,
+	.of_match = sama7_utmi_clk_dt_ids,
+	.id = UCLASS_CLK,
+	.ops = &sama7_utmi_ops,
+	.probe = sama7_utmi_probe,
+};
-- 
2.34.1



More information about the U-Boot mailing list