@@ -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"
@@ -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/
new file mode 100644
@@ -0,0 +1,90 @@
+// 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@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;
+};
+
+static 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;
+}
+
+static 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;
+}
+
+static 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;
+}
+
+static 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))
+ 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),
+};
new file mode 100644
@@ -0,0 +1,216 @@
+// 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@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 USB_RESET_NAME_MAX_LEN 16
+
+#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(USB_UTMI, utmi_clk->id), clk);
+
+ return clk;
+}
+
+static int sama7_utmi_probe(struct udevice *dev)
+{
+ struct clk *utmi_parent_clk, *utmi_clk[ARRAY_SIZE(sama7_utmick)];
+ char name[USB_RESET_NAME_MAX_LEN];
+ struct sama7_utmi_clk *uclk;
+ struct reset_ctl *phy_reset;
+ struct regmap *regmap_sfr;
+ int ret, i, j;
+
+ 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)) {
+ ret = PTR_ERR(phy_reset);
+ goto err_uclk_register;
+ }
+
+ utmi_clk[i] = 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[i])) {
+ ret = PTR_ERR(utmi_clk[i]);
+ goto err_uclk_register;
+ }
+ }
+
+ return 0;
+
+err_uclk_register:
+ for (j = 0; j < i; j++) {
+ uclk = to_sama7_utmi_clk(utmi_clk[j]);
+ kfree(uclk);
+ }
+
+ return ret;
+};
+
+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(USB_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,
+};