From: Sergiu Moga <sergiu.m...@microchip.com> 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.m...@microchip.com> Tested-by: Mihai Sain <mihai.s...@microchip.com> --- v1 -> v2: - use *_bulk API's v2 -> v3: - use if (CONFIG_IS_ENABLED(...)) v3->v4: - none drivers/phy/Kconfig | 10 ++ drivers/phy/Makefile | 1 + drivers/phy/phy-sama7-usb.c | 90 +++++++++++++ drivers/phy/phy-sama7-utmi-clk.c | 216 +++++++++++++++++++++++++++++++ 4 files changed, 317 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 7a2d54f71d21..999f9793fa3f 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 aca365d219c4..0b609768774a 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 000000000000..200324d81281 --- /dev/null +++ b/drivers/phy/phy-sama7-usb.c @@ -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.m...@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), +}; diff --git a/drivers/phy/phy-sama7-utmi-clk.c b/drivers/phy/phy-sama7-utmi-clk.c new file mode 100644 index 000000000000..2371b4341cd5 --- /dev/null +++ b/drivers/phy/phy-sama7-utmi-clk.c @@ -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.m...@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, +}; -- 2.34.1