diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/block/Kconfig | 2 | ||||
-rw-r--r-- | drivers/phy/Kconfig | 9 | ||||
-rw-r--r-- | drivers/phy/Makefile | 1 | ||||
-rw-r--r-- | drivers/phy/phy-exynos-usbdrd.c | 386 | ||||
-rw-r--r-- | drivers/usb/dwc3/dwc3-generic.c | 1 |
5 files changed, 398 insertions, 1 deletions
diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig index 750b0bd2082..c6c148ebd17 100644 --- a/drivers/block/Kconfig +++ b/drivers/block/Kconfig @@ -96,7 +96,7 @@ config TPL_BLOCK_CACHE config EFI_MEDIA bool "Support EFI media drivers" - default y if EFI || SANDBOX + default y if EFI_CLIENT || SANDBOX select BLK help Enable this to support media devices on top of UEFI. This enables diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index d3fe90d939e..c297fa03ea7 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -259,6 +259,15 @@ config MT76X8_USB_PHY This PHY is found on MT76x8 devices supporting USB. +config PHY_EXYNOS_USBDRD + bool "Exynos SoC series USB DRD PHY driver" + depends on PHY && CLK + depends on ARCH_EXYNOS + select REGMAP + select SYSCON + help + Enable USB DRD PHY support for Exynos SoC series. + config PHY_MTK_TPHY bool "MediaTek T-PHY Driver" depends on PHY diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index b4d01fc700d..98c1ef8683b 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -35,6 +35,7 @@ obj-$(CONFIG_KEYSTONE_USB_PHY) += keystone-usb-phy.o obj-$(CONFIG_MT7620_USB_PHY) += mt7620-usb-phy.o obj-$(CONFIG_MT76X8_USB_PHY) += mt76x8-usb-phy.o obj-$(CONFIG_PHY_DA8XX_USB) += phy-da8xx-usb.o +obj-$(CONFIG_PHY_EXYNOS_USBDRD) += phy-exynos-usbdrd.o 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 diff --git a/drivers/phy/phy-exynos-usbdrd.c b/drivers/phy/phy-exynos-usbdrd.c new file mode 100644 index 00000000000..db5815ed184 --- /dev/null +++ b/drivers/phy/phy-exynos-usbdrd.c @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Linaro Ltd. + * Sam Protsenko <semen.protsenko@linaro.org> + * + * Samsung Exynos SoC series USB DRD PHY driver. + * Based on Linux kernel PHY driver: drivers/phy/samsung/phy-exynos5-usbdrd.c + */ + +#include <clk.h> +#include <dm.h> +#include <generic-phy.h> +#include <regmap.h> +#include <syscon.h> +#include <asm/io.h> +#include <dm/device_compat.h> +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/delay.h> + +/* Offset of PMU register controlling USB PHY output isolation */ +#define EXYNOS_USBDRD_PHY_CONTROL 0x0704 +#define EXYNOS_PHY_ENABLE BIT(0) + +/* Exynos USB PHY registers */ +#define EXYNOS5_FSEL_9MHZ6 0x0 +#define EXYNOS5_FSEL_10MHZ 0x1 +#define EXYNOS5_FSEL_12MHZ 0x2 +#define EXYNOS5_FSEL_19MHZ2 0x3 +#define EXYNOS5_FSEL_20MHZ 0x4 +#define EXYNOS5_FSEL_24MHZ 0x5 +#define EXYNOS5_FSEL_26MHZ 0x6 +#define EXYNOS5_FSEL_50MHZ 0x7 + +/* Exynos850: USB DRD PHY registers */ +#define EXYNOS850_DRD_LINKCTRL 0x04 +#define LINKCTRL_FORCE_QACT BIT(8) +#define LINKCTRL_BUS_FILTER_BYPASS GENMASK(7, 4) + +#define EXYNOS850_DRD_CLKRST 0x20 +#define CLKRST_LINK_SW_RST BIT(0) +#define CLKRST_PORT_RST BIT(1) +#define CLKRST_PHY_SW_RST BIT(3) + +#define EXYNOS850_DRD_SSPPLLCTL 0x30 +#define SSPPLLCTL_FSEL GENMASK(2, 0) + +#define EXYNOS850_DRD_UTMI 0x50 +#define UTMI_FORCE_SLEEP BIT(0) +#define UTMI_FORCE_SUSPEND BIT(1) +#define UTMI_DM_PULLDOWN BIT(2) +#define UTMI_DP_PULLDOWN BIT(3) +#define UTMI_FORCE_BVALID BIT(4) +#define UTMI_FORCE_VBUSVALID BIT(5) + +#define EXYNOS850_DRD_HSP 0x54 +#define HSP_COMMONONN BIT(8) +#define HSP_EN_UTMISUSPEND BIT(9) +#define HSP_VBUSVLDEXT BIT(12) +#define HSP_VBUSVLDEXTSEL BIT(13) +#define HSP_FSV_OUT_EN BIT(24) + +#define EXYNOS850_DRD_HSP_TEST 0x5c +#define HSP_TEST_SIDDQ BIT(24) + +#define KHZ 1000 +#define MHZ (KHZ * KHZ) + +/** + * struct exynos_usbdrd_phy - driver data for Exynos USB PHY + * @reg_phy: USB PHY controller register memory base + * @clk: clock for register access + * @core_clk: core clock for phy (ref clock) + * @reg_pmu: regmap for PMU block + * @extrefclk: frequency select settings when using 'separate reference clocks' + */ +struct exynos_usbdrd_phy { + void __iomem *reg_phy; + struct clk *clk; + struct clk *core_clk; + struct regmap *reg_pmu; + u32 extrefclk; +}; + +static void exynos_usbdrd_phy_isol(struct regmap *reg_pmu, bool isolate) +{ + unsigned int val; + + if (!reg_pmu) + return; + + val = isolate ? 0 : EXYNOS_PHY_ENABLE; + regmap_update_bits(reg_pmu, EXYNOS_USBDRD_PHY_CONTROL, + EXYNOS_PHY_ENABLE, val); +} + +/* + * Convert the supplied clock rate to the value that can be written to the PHY + * register. + */ +static unsigned int exynos_rate_to_clk(unsigned long rate, u32 *reg) +{ + switch (rate) { + case 9600 * KHZ: + *reg = EXYNOS5_FSEL_9MHZ6; + break; + case 10 * MHZ: + *reg = EXYNOS5_FSEL_10MHZ; + break; + case 12 * MHZ: + *reg = EXYNOS5_FSEL_12MHZ; + break; + case 19200 * KHZ: + *reg = EXYNOS5_FSEL_19MHZ2; + break; + case 20 * MHZ: + *reg = EXYNOS5_FSEL_20MHZ; + break; + case 24 * MHZ: + *reg = EXYNOS5_FSEL_24MHZ; + break; + case 26 * MHZ: + *reg = EXYNOS5_FSEL_26MHZ; + break; + case 50 * MHZ: + *reg = EXYNOS5_FSEL_50MHZ; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void exynos850_usbdrd_utmi_init(struct phy *phy) +{ + struct exynos_usbdrd_phy *phy_drd = dev_get_priv(phy->dev); + void __iomem *regs_base = phy_drd->reg_phy; + u32 reg; + + /* + * Disable HWACG (hardware auto clock gating control). This will force + * QACTIVE signal in Q-Channel interface to HIGH level, to make sure + * the PHY clock is not gated by the hardware. + */ + reg = readl(regs_base + EXYNOS850_DRD_LINKCTRL); + reg |= LINKCTRL_FORCE_QACT; + writel(reg, regs_base + EXYNOS850_DRD_LINKCTRL); + + /* Start PHY Reset (POR=high) */ + reg = readl(regs_base + EXYNOS850_DRD_CLKRST); + reg |= CLKRST_PHY_SW_RST; + writel(reg, regs_base + EXYNOS850_DRD_CLKRST); + + /* Enable UTMI+ */ + reg = readl(regs_base + EXYNOS850_DRD_UTMI); + reg &= ~(UTMI_FORCE_SUSPEND | UTMI_FORCE_SLEEP | UTMI_DP_PULLDOWN | + UTMI_DM_PULLDOWN); + writel(reg, regs_base + EXYNOS850_DRD_UTMI); + + /* Set PHY clock and control HS PHY */ + reg = readl(regs_base + EXYNOS850_DRD_HSP); + reg |= HSP_EN_UTMISUSPEND | HSP_COMMONONN; + writel(reg, regs_base + EXYNOS850_DRD_HSP); + + /* Set VBUS Valid and D+ pull-up control by VBUS pad usage */ + reg = readl(regs_base + EXYNOS850_DRD_LINKCTRL); + reg |= FIELD_PREP(LINKCTRL_BUS_FILTER_BYPASS, 0xf); + writel(reg, regs_base + EXYNOS850_DRD_LINKCTRL); + + reg = readl(regs_base + EXYNOS850_DRD_UTMI); + reg |= UTMI_FORCE_BVALID | UTMI_FORCE_VBUSVALID; + writel(reg, regs_base + EXYNOS850_DRD_UTMI); + + reg = readl(regs_base + EXYNOS850_DRD_HSP); + reg |= HSP_VBUSVLDEXT | HSP_VBUSVLDEXTSEL; + writel(reg, regs_base + EXYNOS850_DRD_HSP); + + reg = readl(regs_base + EXYNOS850_DRD_SSPPLLCTL); + reg &= ~SSPPLLCTL_FSEL; + switch (phy_drd->extrefclk) { + case EXYNOS5_FSEL_50MHZ: + reg |= FIELD_PREP(SSPPLLCTL_FSEL, 7); + break; + case EXYNOS5_FSEL_26MHZ: + reg |= FIELD_PREP(SSPPLLCTL_FSEL, 6); + break; + case EXYNOS5_FSEL_24MHZ: + reg |= FIELD_PREP(SSPPLLCTL_FSEL, 2); + break; + case EXYNOS5_FSEL_20MHZ: + reg |= FIELD_PREP(SSPPLLCTL_FSEL, 1); + break; + case EXYNOS5_FSEL_19MHZ2: + reg |= FIELD_PREP(SSPPLLCTL_FSEL, 0); + break; + default: + dev_warn(phy->dev, "unsupported ref clk: %#.2x\n", + phy_drd->extrefclk); + break; + } + writel(reg, regs_base + EXYNOS850_DRD_SSPPLLCTL); + + /* Power up PHY analog blocks */ + reg = readl(regs_base + EXYNOS850_DRD_HSP_TEST); + reg &= ~HSP_TEST_SIDDQ; + writel(reg, regs_base + EXYNOS850_DRD_HSP_TEST); + + /* Finish PHY reset (POR=low) */ + udelay(10); /* required before doing POR=low */ + reg = readl(regs_base + EXYNOS850_DRD_CLKRST); + reg &= ~(CLKRST_PHY_SW_RST | CLKRST_PORT_RST); + writel(reg, regs_base + EXYNOS850_DRD_CLKRST); + udelay(75); /* required after POR=low for guaranteed PHY clock */ + + /* Disable single ended signal out */ + reg = readl(regs_base + EXYNOS850_DRD_HSP); + reg &= ~HSP_FSV_OUT_EN; + writel(reg, regs_base + EXYNOS850_DRD_HSP); +} + +static void exynos850_usbdrd_utmi_exit(struct phy *phy) +{ + struct exynos_usbdrd_phy *phy_drd = dev_get_priv(phy->dev); + void __iomem *regs_base = phy_drd->reg_phy; + u32 reg; + + /* Set PHY clock and control HS PHY */ + reg = readl(regs_base + EXYNOS850_DRD_UTMI); + reg &= ~(UTMI_DP_PULLDOWN | UTMI_DM_PULLDOWN); + reg |= UTMI_FORCE_SUSPEND | UTMI_FORCE_SLEEP; + writel(reg, regs_base + EXYNOS850_DRD_UTMI); + + /* Power down PHY analog blocks */ + reg = readl(regs_base + EXYNOS850_DRD_HSP_TEST); + reg |= HSP_TEST_SIDDQ; + writel(reg, regs_base + EXYNOS850_DRD_HSP_TEST); + + /* Link reset */ + reg = readl(regs_base + EXYNOS850_DRD_CLKRST); + reg |= CLKRST_LINK_SW_RST; + writel(reg, regs_base + EXYNOS850_DRD_CLKRST); + udelay(10); /* required before doing POR=low */ + reg &= ~CLKRST_LINK_SW_RST; + writel(reg, regs_base + EXYNOS850_DRD_CLKRST); +} + +static int exynos_usbdrd_phy_init(struct phy *phy) +{ + struct exynos_usbdrd_phy *phy_drd = dev_get_priv(phy->dev); + int ret; + + ret = clk_prepare_enable(phy_drd->clk); + if (ret) + return ret; + + exynos850_usbdrd_utmi_init(phy); + + clk_disable_unprepare(phy_drd->clk); + + return 0; +} + +static int exynos_usbdrd_phy_exit(struct phy *phy) +{ + struct exynos_usbdrd_phy *phy_drd = dev_get_priv(phy->dev); + int ret; + + ret = clk_prepare_enable(phy_drd->clk); + if (ret) + return ret; + + exynos850_usbdrd_utmi_exit(phy); + + clk_disable_unprepare(phy_drd->clk); + + return 0; +} + +static int exynos_usbdrd_phy_power_on(struct phy *phy) +{ + struct exynos_usbdrd_phy *phy_drd = dev_get_priv(phy->dev); + int ret; + + dev_dbg(phy->dev, "Request to power_on usbdrd_phy phy\n"); + + ret = clk_prepare_enable(phy_drd->core_clk); + if (ret) + return ret; + + /* Power-on PHY */ + exynos_usbdrd_phy_isol(phy_drd->reg_pmu, false); + + return 0; +} + +static int exynos_usbdrd_phy_power_off(struct phy *phy) +{ + struct exynos_usbdrd_phy *phy_drd = dev_get_priv(phy->dev); + + dev_dbg(phy->dev, "Request to power_off usbdrd_phy phy\n"); + + /* Power-off the PHY */ + exynos_usbdrd_phy_isol(phy_drd->reg_pmu, true); + + clk_disable_unprepare(phy_drd->core_clk); + + return 0; +} + +static int exynos_usbdrd_phy_init_clk(struct udevice *dev) +{ + struct exynos_usbdrd_phy *phy_drd = dev_get_priv(dev); + unsigned long ref_rate; + int err; + + phy_drd->clk = devm_clk_get(dev, "phy"); + if (IS_ERR(phy_drd->clk)) { + err = PTR_ERR(phy_drd->clk); + dev_err(dev, "Failed to get phy clock (err=%d)\n", err); + return err; + } + + phy_drd->core_clk = devm_clk_get(dev, "ref"); + if (IS_ERR(phy_drd->core_clk)) { + err = PTR_ERR(phy_drd->core_clk); + dev_err(dev, "Failed to get ref clock (err=%d)\n", err); + return err; + } + + ref_rate = clk_get_rate(phy_drd->core_clk); + err = exynos_rate_to_clk(ref_rate, &phy_drd->extrefclk); + if (err) { + dev_err(dev, "Clock rate %lu not supported\n", ref_rate); + return err; + } + + return 0; +} + +static int exynos_usbdrd_phy_probe(struct udevice *dev) +{ + struct exynos_usbdrd_phy *phy_drd = dev_get_priv(dev); + int err; + + phy_drd->reg_phy = dev_read_addr_ptr(dev); + if (!phy_drd->reg_phy) + return -EINVAL; + + err = exynos_usbdrd_phy_init_clk(dev); + if (err) + return err; + + phy_drd->reg_pmu = syscon_regmap_lookup_by_phandle(dev, + "samsung,pmu-syscon"); + if (IS_ERR(phy_drd->reg_pmu)) { + err = PTR_ERR(phy_drd->reg_pmu); + dev_err(dev, "Failed to lookup PMU regmap\n"); + return err; + } + + return 0; +} + +static const struct phy_ops exynos_usbdrd_phy_ops = { + .init = exynos_usbdrd_phy_init, + .exit = exynos_usbdrd_phy_exit, + .power_on = exynos_usbdrd_phy_power_on, + .power_off = exynos_usbdrd_phy_power_off, +}; + +static const struct udevice_id exynos_usbdrd_phy_of_match[] = { + { + .compatible = "samsung,exynos850-usbdrd-phy", + }, + { } +}; + +U_BOOT_DRIVER(exynos_usbdrd_phy) = { + .name = "exynos-usbdrd-phy", + .id = UCLASS_PHY, + .of_match = exynos_usbdrd_phy_of_match, + .probe = exynos_usbdrd_phy_probe, + .ops = &exynos_usbdrd_phy_ops, + .priv_auto = sizeof(struct exynos_usbdrd_phy), +}; diff --git a/drivers/usb/dwc3/dwc3-generic.c b/drivers/usb/dwc3/dwc3-generic.c index 21452ad1569..3cda2b74b7e 100644 --- a/drivers/usb/dwc3/dwc3-generic.c +++ b/drivers/usb/dwc3/dwc3-generic.c @@ -704,6 +704,7 @@ static const struct udevice_id dwc3_glue_ids[] = { { .compatible = "fsl,imx8mp-dwc3", .data = (ulong)&imx8mp_ops }, { .compatible = "fsl,imx8mq-dwc3" }, { .compatible = "intel,tangier-dwc3" }, + { .compatible = "samsung,exynos850-dwusb3" }, { } }; |