mirror of
https://source.denx.de/u-boot/u-boot.git
synced 2025-08-06 07:17:01 +02:00
phy: samsung: Add Exynos USB DRD PHY driver
Add DM driver for Exynos USB PHY controllers. For now it only supports Exynos850 SoC. Only UTMI+ (USB 2.0) PHY interface is implemented, as Exynos850 doesn't support USB 3.0. Only two clocks are used for this controller: - phy: bus clock, used for PHY registers access - ref: PHY reference clock (OSCCLK) Ported from Linux kernel: drivers/phy/samsung/phy-exynos5-usbdrd.c Signed-off-by: Sam Protsenko <semen.protsenko@linaro.org> Reviewed-by: Mattijs Korpershoek <mkorpershoek@kernel.org> Reviewed-by: Minkyu Kang <mk7.kang@samsung.com> Signed-off-by: Minkyu Kang <mk7.kang@samsung.com>
This commit is contained in:
parent
3532f1f5ed
commit
80c1606d13
@ -602,6 +602,7 @@ ARM SAMSUNG EXYNOS850 SOC
|
||||
M: Sam Protsenko <semen.protsenko@linaro.org>
|
||||
S: Maintained
|
||||
F: drivers/clk/exynos/clk-exynos850.c
|
||||
F: drivers/phy/phy-exynos-usbdrd.c
|
||||
F: drivers/pinctrl/exynos/pinctrl-exynos850.c
|
||||
|
||||
ARM SAMSUNG SOC DRIVERS
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
386
drivers/phy/phy-exynos-usbdrd.c
Normal file
386
drivers/phy/phy-exynos-usbdrd.c
Normal file
@ -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),
|
||||
};
|
Loading…
Reference in New Issue
Block a user