diff options
Diffstat (limited to 'drivers/phy')
-rw-r--r-- | drivers/phy/Kconfig | 13 | ||||
-rw-r--r-- | drivers/phy/Makefile | 1 | ||||
-rw-r--r-- | drivers/phy/allwinner/Kconfig | 13 | ||||
-rw-r--r-- | drivers/phy/allwinner/Makefile | 6 | ||||
-rw-r--r-- | drivers/phy/allwinner/phy-sun4i-usb.c | 575 | ||||
-rw-r--r-- | drivers/phy/bcm6318-usbh-phy.c | 10 | ||||
-rw-r--r-- | drivers/phy/bcm6348-usbh-phy.c | 10 | ||||
-rw-r--r-- | drivers/phy/bcm6358-usbh-phy.c | 10 | ||||
-rw-r--r-- | drivers/phy/bcm6368-usbh-phy.c | 9 | ||||
-rw-r--r-- | drivers/phy/phy-stm32-usbphyc.c | 402 |
10 files changed, 1022 insertions, 27 deletions
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 119edec2048..8fc2295d03a 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -110,6 +110,19 @@ config STI_USB_PHY used by USB2 and USB3 Host controllers available on STiH407 SoC families. +config PHY_STM32_USBPHYC + tristate "STMicroelectronics STM32 SoC USB HS PHY driver" + depends on PHY && ARCH_STM32MP + help + Enable this to support the High-Speed USB transceiver that is part of + STMicroelectronics STM32 SoCs. + + This driver controls the entire USB PHY block: the USB PHY controller + (USBPHYC) and the two 8-bit wide UTMI+ interface. First interface is + used by an HS USB Host controller, and the second one is shared + between an HS USB OTG controller and an HS USB Host controller, + selected by an USB switch. + config MESON_GXL_USB_PHY bool "Amlogic Meson GXL USB PHYs" depends on PHY && ARCH_MESON && MESON_GXL diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 5c8711d6a4f..ba0803cd040 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -12,4 +12,5 @@ obj-$(CONFIG_BCM6368_USBH_PHY) += bcm6368-usbh-phy.o obj-$(CONFIG_PHY_SANDBOX) += sandbox-phy.o obj-$(CONFIG_$(SPL_)PIPE3_PHY) += ti-pipe3-phy.o obj-$(CONFIG_STI_USB_PHY) += sti_usb_phy.o +obj-$(CONFIG_PHY_STM32_USBPHYC) += phy-stm32-usbphyc.o obj-$(CONFIG_MESON_GXL_USB_PHY) += meson-gxl-usb2.o meson-gxl-usb3.o diff --git a/drivers/phy/allwinner/Kconfig b/drivers/phy/allwinner/Kconfig new file mode 100644 index 00000000000..dba3bae61c4 --- /dev/null +++ b/drivers/phy/allwinner/Kconfig @@ -0,0 +1,13 @@ +# +# Phy drivers for Allwinner platforms +# +config PHY_SUN4I_USB + bool "Allwinner Sun4I USB PHY driver" + depends on ARCH_SUNXI + select PHY + help + Enable this to support the transceiver that is part of Allwinner + sunxi SoCs. + + This driver controls the entire USB PHY block, both the USB OTG + parts, as well as the 2 regular USB 2 host PHYs. diff --git a/drivers/phy/allwinner/Makefile b/drivers/phy/allwinner/Makefile new file mode 100644 index 00000000000..5ed2702e4f0 --- /dev/null +++ b/drivers/phy/allwinner/Makefile @@ -0,0 +1,6 @@ +# Copyright (C) 2016 Amarula Solutions +# +# SPDX-License-Identifier: GPL-2.0+ +# + +obj-$(CONFIG_PHY_SUN4I_USB) += phy-sun4i-usb.o diff --git a/drivers/phy/allwinner/phy-sun4i-usb.c b/drivers/phy/allwinner/phy-sun4i-usb.c new file mode 100644 index 00000000000..2b3cf48025c --- /dev/null +++ b/drivers/phy/allwinner/phy-sun4i-usb.c @@ -0,0 +1,575 @@ +/* + * Allwinner sun4i USB PHY driver + * + * Copyright (C) 2017 Jagan Teki <jagan@amarulasolutions.com> + * Copyright (C) 2015 Hans de Goede <hdegoede@redhat.com> + * Copyright (C) 2014 Roman Byshko <rbyshko@gmail.com> + * + * Modelled arch/arm/mach-sunxi/usb_phy.c to compatible with generic-phy. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <dm/device.h> +#include <generic-phy.h> +#include <phy-sun4i-usb.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch/cpu.h> + +#define REG_ISCR 0x00 +#define REG_PHYCTL_A10 0x04 +#define REG_PHYBIST 0x08 +#define REG_PHYTUNE 0x0c +#define REG_PHYCTL_A33 0x10 +#define REG_PHY_OTGCTL 0x20 +#define REG_PMU_UNK1 0x10 + +/* Common Control Bits for Both PHYs */ +#define PHY_PLL_BW 0x03 +#define PHY_RES45_CAL_EN 0x0c + +/* Private Control Bits for Each PHY */ +#define PHY_TX_AMPLITUDE_TUNE 0x20 +#define PHY_TX_SLEWRATE_TUNE 0x22 +#define PHY_DISCON_TH_SEL 0x2a +#define PHY_SQUELCH_DETECT 0x3c + +#define PHYCTL_DATA BIT(7) +#define OTGCTL_ROUTE_MUSB BIT(0) + +#define PHY_TX_RATE BIT(4) +#define PHY_TX_MAGNITUDE BIT(2) +#define PHY_TX_AMPLITUDE_LEN 5 + +#define PHY_RES45_CAL_DATA BIT(0) +#define PHY_RES45_CAL_LEN 1 +#define PHY_DISCON_TH_LEN 2 + +#define SUNXI_AHB_ICHR8_EN BIT(10) +#define SUNXI_AHB_INCR4_BURST_EN BIT(9) +#define SUNXI_AHB_INCRX_ALIGN_EN BIT(8) +#define SUNXI_ULPI_BYPASS_EN BIT(0) + +/* A83T specific control bits for PHY0 */ +#define PHY_CTL_VBUSVLDEXT BIT(5) +#define PHY_CTL_SIDDQ BIT(3) + +/* A83T specific control bits for PHY2 HSIC */ +#define SUNXI_EHCI_HS_FORCE BIT(20) +#define SUNXI_HSIC_CONNECT_INT BIT(16) +#define SUNXI_HSIC BIT(1) + +#define MAX_PHYS 4 + +enum sun4i_usb_phy_type { + sun4i_a10_phy, + sun6i_a31_phy, + sun8i_a33_phy, + sun8i_a83t_phy, + sun8i_h3_phy, + sun8i_v3s_phy, + sun50i_a64_phy, +}; + +struct sun4i_usb_phy_cfg { + int num_phys; + enum sun4i_usb_phy_type type; + u32 disc_thresh; + u8 phyctl_offset; + bool enable_pmu_unk1; + bool phy0_dual_route; +}; + +struct sun4i_usb_phy_info { + const char *gpio_vbus; + const char *gpio_vbus_det; + const char *gpio_id_det; + int rst_mask; +} phy_info[] = { + { + .gpio_vbus = CONFIG_USB0_VBUS_PIN, + .gpio_vbus_det = CONFIG_USB0_VBUS_DET, + .gpio_id_det = CONFIG_USB0_ID_DET, + .rst_mask = (CCM_USB_CTRL_PHY0_RST | CCM_USB_CTRL_PHY0_CLK), + }, + { + .gpio_vbus = CONFIG_USB1_VBUS_PIN, + .gpio_vbus_det = NULL, + .gpio_id_det = NULL, + .rst_mask = (CCM_USB_CTRL_PHY1_RST | CCM_USB_CTRL_PHY1_CLK), + }, + { + .gpio_vbus = CONFIG_USB2_VBUS_PIN, + .gpio_vbus_det = NULL, + .gpio_id_det = NULL, +#ifdef CONFIG_MACH_SUN8I_A83T + .rst_mask = (CCM_USB_CTRL_HSIC_RST | CCM_USB_CTRL_HSIC_CLK | + CCM_USB_CTRL_12M_CLK), +#else + .rst_mask = (CCM_USB_CTRL_PHY2_RST | CCM_USB_CTRL_PHY2_CLK), +#endif + }, + { + .gpio_vbus = CONFIG_USB3_VBUS_PIN, + .gpio_vbus_det = NULL, + .gpio_id_det = NULL, +#ifdef CONFIG_MACH_SUN6I + .rst_mask = (CCM_USB_CTRL_PHY3_RST | CCM_USB_CTRL_PHY3_CLK), +#endif + }, +}; + +struct sun4i_usb_phy_plat { + void __iomem *pmu; + int power_on_count; + int gpio_vbus; + int gpio_vbus_det; + int gpio_id_det; + int rst_mask; + int id; +}; + +struct sun4i_usb_phy_data { + void __iomem *base; + struct sunxi_ccm_reg *ccm; + const struct sun4i_usb_phy_cfg *cfg; + struct sun4i_usb_phy_plat *usb_phy; +}; + +static int initial_usb_scan_delay = CONFIG_INITIAL_USB_SCAN_DELAY; + +static void sun4i_usb_phy_write(struct phy *phy, u32 addr, u32 data, int len) +{ + struct sun4i_usb_phy_data *phy_data = dev_get_priv(phy->dev); + struct sun4i_usb_phy_plat *usb_phy = &phy_data->usb_phy[phy->id]; + u32 temp, usbc_bit = BIT(usb_phy->id * 2); + void __iomem *phyctl = phy_data->base + phy_data->cfg->phyctl_offset; + int i; + + if (phy_data->cfg->phyctl_offset == REG_PHYCTL_A33) { + /* SoCs newer than A33 need us to set phyctl to 0 explicitly */ + writel(0, phyctl); + } + + for (i = 0; i < len; i++) { + temp = readl(phyctl); + + /* clear the address portion */ + temp &= ~(0xff << 8); + + /* set the address */ + temp |= ((addr + i) << 8); + writel(temp, phyctl); + + /* set the data bit and clear usbc bit*/ + temp = readb(phyctl); + if (data & 0x1) + temp |= PHYCTL_DATA; + else + temp &= ~PHYCTL_DATA; + temp &= ~usbc_bit; + writeb(temp, phyctl); + + /* pulse usbc_bit */ + temp = readb(phyctl); + temp |= usbc_bit; + writeb(temp, phyctl); + + temp = readb(phyctl); + temp &= ~usbc_bit; + writeb(temp, phyctl); + + data >>= 1; + } +} + +static void sun4i_usb_phy_passby(struct phy *phy, bool enable) +{ + struct sun4i_usb_phy_data *data = dev_get_priv(phy->dev); + struct sun4i_usb_phy_plat *usb_phy = &data->usb_phy[phy->id]; + u32 bits, reg_value; + + if (!usb_phy->pmu) + return; + + bits = SUNXI_AHB_ICHR8_EN | SUNXI_AHB_INCR4_BURST_EN | + SUNXI_AHB_INCRX_ALIGN_EN | SUNXI_ULPI_BYPASS_EN; + + /* A83T USB2 is HSIC */ + if (data->cfg->type == sun8i_a83t_phy && usb_phy->id == 2) + bits |= SUNXI_EHCI_HS_FORCE | SUNXI_HSIC_CONNECT_INT | + SUNXI_HSIC; + + reg_value = readl(usb_phy->pmu); + + if (enable) + reg_value |= bits; + else + reg_value &= ~bits; + + writel(reg_value, usb_phy->pmu); +} + +static int sun4i_usb_phy_power_on(struct phy *phy) +{ + struct sun4i_usb_phy_data *data = dev_get_priv(phy->dev); + struct sun4i_usb_phy_plat *usb_phy = &data->usb_phy[phy->id]; + + if (initial_usb_scan_delay) { + mdelay(initial_usb_scan_delay); + initial_usb_scan_delay = 0; + } + + usb_phy->power_on_count++; + if (usb_phy->power_on_count != 1) + return 0; + + if (usb_phy->gpio_vbus >= 0) + gpio_set_value(usb_phy->gpio_vbus, SUNXI_GPIO_PULL_UP); + + return 0; +} + +static int sun4i_usb_phy_power_off(struct phy *phy) +{ + struct sun4i_usb_phy_data *data = dev_get_priv(phy->dev); + struct sun4i_usb_phy_plat *usb_phy = &data->usb_phy[phy->id]; + + usb_phy->power_on_count--; + if (usb_phy->power_on_count != 0) + return 0; + + if (usb_phy->gpio_vbus >= 0) + gpio_set_value(usb_phy->gpio_vbus, SUNXI_GPIO_PULL_DISABLE); + + return 0; +} + +static void sun4i_usb_phy0_reroute(struct sun4i_usb_phy_data *data, bool id_det) +{ + u32 regval; + + regval = readl(data->base + REG_PHY_OTGCTL); + if (!id_det) { + /* Host mode. Route phy0 to EHCI/OHCI */ + regval &= ~OTGCTL_ROUTE_MUSB; + } else { + /* Peripheral mode. Route phy0 to MUSB */ + regval |= OTGCTL_ROUTE_MUSB; + } + writel(regval, data->base + REG_PHY_OTGCTL); +} + +static int sun4i_usb_phy_init(struct phy *phy) +{ + struct sun4i_usb_phy_data *data = dev_get_priv(phy->dev); + struct sun4i_usb_phy_plat *usb_phy = &data->usb_phy[phy->id]; + u32 val; + + setbits_le32(&data->ccm->usb_clk_cfg, usb_phy->rst_mask); + + if (data->cfg->type == sun8i_a83t_phy) { + if (phy->id == 0) { + val = readl(data->base + data->cfg->phyctl_offset); + val |= PHY_CTL_VBUSVLDEXT; + val &= ~PHY_CTL_SIDDQ; + writel(val, data->base + data->cfg->phyctl_offset); + } + } else { + if (usb_phy->pmu && data->cfg->enable_pmu_unk1) { + val = readl(usb_phy->pmu + REG_PMU_UNK1); + writel(val & ~2, usb_phy->pmu + REG_PMU_UNK1); + } + + if (usb_phy->id == 0) + sun4i_usb_phy_write(phy, PHY_RES45_CAL_EN, + PHY_RES45_CAL_DATA, + PHY_RES45_CAL_LEN); + + /* Adjust PHY's magnitude and rate */ + sun4i_usb_phy_write(phy, PHY_TX_AMPLITUDE_TUNE, + PHY_TX_MAGNITUDE | PHY_TX_RATE, + PHY_TX_AMPLITUDE_LEN); + + /* Disconnect threshold adjustment */ + sun4i_usb_phy_write(phy, PHY_DISCON_TH_SEL, + data->cfg->disc_thresh, PHY_DISCON_TH_LEN); + } + + if (usb_phy->id != 0) + sun4i_usb_phy_passby(phy, true); + + sun4i_usb_phy0_reroute(data, true); + + return 0; +} + +static int sun4i_usb_phy_exit(struct phy *phy) +{ + struct sun4i_usb_phy_data *data = dev_get_priv(phy->dev); + struct sun4i_usb_phy_plat *usb_phy = &data->usb_phy[phy->id]; + + if (phy->id == 0) { + if (data->cfg->type == sun8i_a83t_phy) { + void __iomem *phyctl = data->base + + data->cfg->phyctl_offset; + + writel(readl(phyctl) | PHY_CTL_SIDDQ, phyctl); + } + } + + sun4i_usb_phy_passby(phy, false); + + clrbits_le32(&data->ccm->usb_clk_cfg, usb_phy->rst_mask); + + return 0; +} + +static int sun4i_usb_phy_xlate(struct phy *phy, + struct ofnode_phandle_args *args) +{ + struct sun4i_usb_phy_data *data = dev_get_priv(phy->dev); + + if (args->args_count >= data->cfg->num_phys) + return -EINVAL; + + if (args->args_count) + phy->id = args->args[0]; + else + phy->id = 0; + + debug("%s: phy_id = %ld\n", __func__, phy->id); + return 0; +} + +int sun4i_usb_phy_vbus_detect(struct phy *phy) +{ + struct sun4i_usb_phy_data *data = dev_get_priv(phy->dev); + struct sun4i_usb_phy_plat *usb_phy = &data->usb_phy[phy->id]; + int err, retries = 3; + + debug("%s: id_det = %d\n", __func__, usb_phy->gpio_id_det); + + if (usb_phy->gpio_vbus_det < 0) + return usb_phy->gpio_vbus_det; + + err = gpio_get_value(usb_phy->gpio_vbus_det); + /* + * Vbus may have been provided by the board and just been turned of + * some milliseconds ago on reset, what we're measuring then is a + * residual charge on Vbus, sleep a bit and try again. + */ + while (err > 0 && retries--) { + mdelay(100); + err = gpio_get_value(usb_phy->gpio_vbus_det); + } + + return err; +} + +int sun4i_usb_phy_id_detect(struct phy *phy) +{ + struct sun4i_usb_phy_data *data = dev_get_priv(phy->dev); + struct sun4i_usb_phy_plat *usb_phy = &data->usb_phy[phy->id]; + + debug("%s: id_det = %d\n", __func__, usb_phy->gpio_id_det); + + if (usb_phy->gpio_id_det < 0) + return usb_phy->gpio_id_det; + + return gpio_get_value(usb_phy->gpio_id_det); +} + +void sun4i_usb_phy_set_squelch_detect(struct phy *phy, bool enabled) +{ + sun4i_usb_phy_write(phy, PHY_SQUELCH_DETECT, enabled ? 0 : 2, 2); +} + +static struct phy_ops sun4i_usb_phy_ops = { + .of_xlate = sun4i_usb_phy_xlate, + .init = sun4i_usb_phy_init, + .power_on = sun4i_usb_phy_power_on, + .power_off = sun4i_usb_phy_power_off, + .exit = sun4i_usb_phy_exit, +}; + +static int sun4i_usb_phy_probe(struct udevice *dev) +{ + struct sun4i_usb_phy_plat *plat = dev_get_platdata(dev); + struct sun4i_usb_phy_data *data = dev_get_priv(dev); + int i, ret; + + data->cfg = (const struct sun4i_usb_phy_cfg *)dev_get_driver_data(dev); + if (!data->cfg) + return -EINVAL; + + data->base = (void __iomem *)devfdt_get_addr_name(dev, "phy_ctrl"); + if (IS_ERR(data->base)) + return PTR_ERR(data->base); + + data->ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + if (IS_ERR(data->ccm)) + return PTR_ERR(data->ccm); + + data->usb_phy = plat; + for (i = 0; i < data->cfg->num_phys; i++) { + struct sun4i_usb_phy_plat *phy = &plat[i]; + struct sun4i_usb_phy_info *info = &phy_info[i]; + char name[16]; + + phy->gpio_vbus = sunxi_name_to_gpio(info->gpio_vbus); + if (phy->gpio_vbus >= 0) { + ret = gpio_request(phy->gpio_vbus, "usb_vbus"); + if (ret) + return ret; + ret = gpio_direction_output(phy->gpio_vbus, 0); + if (ret) + return ret; + } + + phy->gpio_vbus_det = sunxi_name_to_gpio(info->gpio_vbus_det); + if (phy->gpio_vbus_det >= 0) { + ret = gpio_request(phy->gpio_vbus_det, "usb_vbus_det"); + if (ret) + return ret; + ret = gpio_direction_input(phy->gpio_vbus_det); + if (ret) + return ret; + } + + phy->gpio_id_det = sunxi_name_to_gpio(info->gpio_id_det); + if (phy->gpio_id_det >= 0) { + ret = gpio_request(phy->gpio_id_det, "usb_id_det"); + if (ret) + return ret; + ret = gpio_direction_input(phy->gpio_id_det); + if (ret) + return ret; + sunxi_gpio_set_pull(phy->gpio_id_det, SUNXI_GPIO_PULL_UP); + } + + if (i || data->cfg->phy0_dual_route) { + snprintf(name, sizeof(name), "pmu%d", i); + phy->pmu = (void __iomem *)devfdt_get_addr_name(dev, name); + if (IS_ERR(phy->pmu)) + return PTR_ERR(phy->pmu); + } + + phy->id = i; + phy->rst_mask = info->rst_mask; + }; + + setbits_le32(&data->ccm->usb_clk_cfg, CCM_USB_CTRL_PHYGATE); + + debug("Allwinner Sun4I USB PHY driver loaded\n"); + return 0; +} + +static const struct sun4i_usb_phy_cfg sun4i_a10_cfg = { + .num_phys = 3, + .type = sun4i_a10_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A10, + .enable_pmu_unk1 = false, +}; + +static const struct sun4i_usb_phy_cfg sun5i_a13_cfg = { + .num_phys = 2, + .type = sun4i_a10_phy, + .disc_thresh = 2, + .phyctl_offset = REG_PHYCTL_A10, + .enable_pmu_unk1 = false, +}; + +static const struct sun4i_usb_phy_cfg sun6i_a31_cfg = { + .num_phys = 3, + .type = sun6i_a31_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A10, + .enable_pmu_unk1 = false, +}; + +static const struct sun4i_usb_phy_cfg sun7i_a20_cfg = { + .num_phys = 3, + .type = sun4i_a10_phy, + .disc_thresh = 2, + .phyctl_offset = REG_PHYCTL_A10, + .enable_pmu_unk1 = false, +}; + +static const struct sun4i_usb_phy_cfg sun8i_a23_cfg = { + .num_phys = 2, + .type = sun4i_a10_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A10, + .enable_pmu_unk1 = false, +}; + +static const struct sun4i_usb_phy_cfg sun8i_a33_cfg = { + .num_phys = 2, + .type = sun8i_a33_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A33, + .enable_pmu_unk1 = false, +}; + +static const struct sun4i_usb_phy_cfg sun8i_a83t_cfg = { + .num_phys = 3, + .type = sun8i_a83t_phy, + .phyctl_offset = REG_PHYCTL_A33, +}; + +static const struct sun4i_usb_phy_cfg sun8i_h3_cfg = { + .num_phys = 4, + .type = sun8i_h3_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A33, + .enable_pmu_unk1 = true, + .phy0_dual_route = true, +}; + +static const struct sun4i_usb_phy_cfg sun8i_v3s_cfg = { + .num_phys = 1, + .type = sun8i_v3s_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A33, + .enable_pmu_unk1 = true, + .phy0_dual_route = true, +}; + +static const struct sun4i_usb_phy_cfg sun50i_a64_cfg = { + .num_phys = 2, + .type = sun50i_a64_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A33, + .enable_pmu_unk1 = true, + .phy0_dual_route = true, +}; + +static const struct udevice_id sun4i_usb_phy_ids[] = { + { .compatible = "allwinner,sun4i-a10-usb-phy", .data = (ulong)&sun4i_a10_cfg }, + { .compatible = "allwinner,sun5i-a13-usb-phy", .data = (ulong)&sun5i_a13_cfg }, + { .compatible = "allwinner,sun6i-a31-usb-phy", .data = (ulong)&sun6i_a31_cfg }, + { .compatible = "allwinner,sun7i-a20-usb-phy", .data = (ulong)&sun7i_a20_cfg }, + { .compatible = "allwinner,sun8i-a23-usb-phy", .data = (ulong)&sun8i_a23_cfg }, + { .compatible = "allwinner,sun8i-a33-usb-phy", .data = (ulong)&sun8i_a33_cfg }, + { .compatible = "allwinner,sun8i-a83t-usb-phy", .data = (ulong)&sun8i_a83t_cfg }, + { .compatible = "allwinner,sun8i-h3-usb-phy", .data = (ulong)&sun8i_h3_cfg }, + { .compatible = "allwinner,sun8i-v3s-usb-phy", .data = (ulong)&sun8i_v3s_cfg }, + { .compatible = "allwinner,sun50i-a64-usb-phy", .data = (ulong)&sun50i_a64_cfg}, + { } +}; + +U_BOOT_DRIVER(sun4i_usb_phy) = { + .name = "sun4i_usb_phy", + .id = UCLASS_PHY, + .of_match = sun4i_usb_phy_ids, + .ops = &sun4i_usb_phy_ops, + .probe = sun4i_usb_phy_probe, + .platdata_auto_alloc_size = sizeof(struct sun4i_usb_phy_plat[MAX_PHYS]), + .priv_auto_alloc_size = sizeof(struct sun4i_usb_phy_data), +}; diff --git a/drivers/phy/bcm6318-usbh-phy.c b/drivers/phy/bcm6318-usbh-phy.c index f5bcf188afa..de055a3585f 100644 --- a/drivers/phy/bcm6318-usbh-phy.c +++ b/drivers/phy/bcm6318-usbh-phy.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Copyright (C) 2018 Ãlvaro Fernández Rojas <noltari@gmail.com> + * Copyright (C) 2018 Álvaro Fernández Rojas <noltari@gmail.com> * * Derived from linux/arch/mips/bcm63xx/usb-common.c: * Copyright 2008 Maxime Bizon <mbizon@freebox.fr> @@ -79,16 +79,12 @@ static int bcm6318_usbh_probe(struct udevice *dev) struct power_domain pwr_dom; struct reset_ctl rst_ctl; struct clk clk; - fdt_addr_t addr; - fdt_size_t size; int ret; - addr = devfdt_get_addr_size_index(dev, 0, &size); - if (addr == FDT_ADDR_T_NONE) + priv->regs = dev_remap_addr(dev); + if (!priv->regs) return -EINVAL; - priv->regs = ioremap(addr, size); - /* enable usbh clock */ ret = clk_get_by_name(dev, "usbh", &clk); if (ret < 0) diff --git a/drivers/phy/bcm6348-usbh-phy.c b/drivers/phy/bcm6348-usbh-phy.c index f305738c0e3..e7761e3b286 100644 --- a/drivers/phy/bcm6348-usbh-phy.c +++ b/drivers/phy/bcm6348-usbh-phy.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Copyright (C) 2018 Ãlvaro Fernández Rojas <noltari@gmail.com> + * Copyright (C) 2018 Álvaro Fernández Rojas <noltari@gmail.com> * * Derived from linux/arch/mips/bcm63xx/usb-common.c: * Copyright 2008 Maxime Bizon <mbizon@freebox.fr> @@ -44,16 +44,12 @@ static int bcm6348_usbh_probe(struct udevice *dev) struct bcm6348_usbh_priv *priv = dev_get_priv(dev); struct reset_ctl rst_ctl; struct clk clk; - fdt_addr_t addr; - fdt_size_t size; int ret; - addr = devfdt_get_addr_size_index(dev, 0, &size); - if (addr == FDT_ADDR_T_NONE) + priv->regs = dev_remap_addr(dev); + if (!priv->regs) return -EINVAL; - priv->regs = ioremap(addr, size); - /* enable usbh clock */ ret = clk_get_by_name(dev, "usbh", &clk); if (ret < 0) diff --git a/drivers/phy/bcm6358-usbh-phy.c b/drivers/phy/bcm6358-usbh-phy.c index 9eb641a3846..189a1c11d38 100644 --- a/drivers/phy/bcm6358-usbh-phy.c +++ b/drivers/phy/bcm6358-usbh-phy.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Copyright (C) 2018 Ãlvaro Fernández Rojas <noltari@gmail.com> + * Copyright (C) 2018 Álvaro Fernández Rojas <noltari@gmail.com> * * Derived from linux/arch/mips/bcm63xx/usb-common.c: * Copyright 2008 Maxime Bizon <mbizon@freebox.fr> @@ -57,16 +57,12 @@ static int bcm6358_usbh_probe(struct udevice *dev) { struct bcm6358_usbh_priv *priv = dev_get_priv(dev); struct reset_ctl rst_ctl; - fdt_addr_t addr; - fdt_size_t size; int ret; - addr = devfdt_get_addr_size_index(dev, 0, &size); - if (addr == FDT_ADDR_T_NONE) + priv->regs = dev_remap_addr(dev); + if (!priv->regs) return -EINVAL; - priv->regs = ioremap(addr, size); - /* perform reset */ ret = reset_get_by_index(dev, 0, &rst_ctl); if (ret < 0) diff --git a/drivers/phy/bcm6368-usbh-phy.c b/drivers/phy/bcm6368-usbh-phy.c index 02197ed4253..99da97aa0cd 100644 --- a/drivers/phy/bcm6368-usbh-phy.c +++ b/drivers/phy/bcm6368-usbh-phy.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Copyright (C) 2018 Ãlvaro Fernández Rojas <noltari@gmail.com> + * Copyright (C) 2018 Álvaro Fernández Rojas <noltari@gmail.com> * * Derived from linux/arch/mips/bcm63xx/usb-common.c: * Copyright 2008 Maxime Bizon <mbizon@freebox.fr> @@ -116,15 +116,12 @@ static int bcm6368_usbh_probe(struct udevice *dev) #endif struct reset_ctl rst_ctl; struct clk clk; - fdt_addr_t addr; - fdt_size_t size; int ret; - addr = devfdt_get_addr_size_index(dev, 0, &size); - if (addr == FDT_ADDR_T_NONE) + priv->regs = dev_remap_addr(dev); + if (!priv->regs) return -EINVAL; - priv->regs = ioremap(addr, size); priv->hw = hw; /* enable usbh clock */ diff --git a/drivers/phy/phy-stm32-usbphyc.c b/drivers/phy/phy-stm32-usbphyc.c new file mode 100644 index 00000000000..8e98b4b627b --- /dev/null +++ b/drivers/phy/phy-stm32-usbphyc.c @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause +/* + * Copyright (C) 2018, STMicroelectronics - All Rights Reserved + */ + +#include <common.h> +#include <clk.h> +#include <div64.h> +#include <dm.h> +#include <fdtdec.h> +#include <generic-phy.h> +#include <reset.h> +#include <syscon.h> +#include <usb.h> +#include <asm/io.h> +#include <linux/bitops.h> +#include <power/regulator.h> + +/* USBPHYC registers */ +#define STM32_USBPHYC_PLL 0x0 +#define STM32_USBPHYC_MISC 0x8 + +/* STM32_USBPHYC_PLL bit fields */ +#define PLLNDIV GENMASK(6, 0) +#define PLLNDIV_SHIFT 0 +#define PLLFRACIN GENMASK(25, 10) +#define PLLFRACIN_SHIFT 10 +#define PLLEN BIT(26) +#define PLLSTRB BIT(27) +#define PLLSTRBYP BIT(28) +#define PLLFRACCTL BIT(29) +#define PLLDITHEN0 BIT(30) +#define PLLDITHEN1 BIT(31) + +/* STM32_USBPHYC_MISC bit fields */ +#define SWITHOST BIT(0) + +#define MAX_PHYS 2 + +#define PLL_LOCK_TIME_US 100 +#define PLL_PWR_DOWN_TIME_US 5 +#define PLL_FVCO 2880 /* in MHz */ +#define PLL_INFF_MIN_RATE 19200000 /* in Hz */ +#define PLL_INFF_MAX_RATE 38400000 /* in Hz */ + +struct pll_params { + u8 ndiv; + u16 frac; +}; + +struct stm32_usbphyc { + fdt_addr_t base; + struct clk clk; + struct stm32_usbphyc_phy { + struct udevice *vdd; + struct udevice *vdda1v1; + struct udevice *vdda1v8; + int index; + bool init; + bool powered; + } phys[MAX_PHYS]; +}; + +void stm32_usbphyc_get_pll_params(u32 clk_rate, struct pll_params *pll_params) +{ + unsigned long long fvco, ndiv, frac; + + /* + * | FVCO = INFF*2*(NDIV + FRACT/2^16 ) when DITHER_DISABLE[1] = 1 + * | FVCO = 2880MHz + * | NDIV = integer part of input bits to set the LDF + * | FRACT = fractional part of input bits to set the LDF + * => PLLNDIV = integer part of (FVCO / (INFF*2)) + * => PLLFRACIN = fractional part of(FVCO / INFF*2) * 2^16 + * <=> PLLFRACIN = ((FVCO / (INFF*2)) - PLLNDIV) * 2^16 + */ + fvco = (unsigned long long)PLL_FVCO * 1000000; /* In Hz */ + + ndiv = fvco; + do_div(ndiv, (clk_rate * 2)); + pll_params->ndiv = (u8)ndiv; + + frac = fvco * (1 << 16); + do_div(frac, (clk_rate * 2)); + frac = frac - (ndiv * (1 << 16)); + pll_params->frac = (u16)frac; +} + +static int stm32_usbphyc_pll_init(struct stm32_usbphyc *usbphyc) +{ + struct pll_params pll_params; + u32 clk_rate = clk_get_rate(&usbphyc->clk); + u32 usbphyc_pll; + + if ((clk_rate < PLL_INFF_MIN_RATE) || (clk_rate > PLL_INFF_MAX_RATE)) { + pr_debug("%s: input clk freq (%dHz) out of range\n", + __func__, clk_rate); + return -EINVAL; + } + + stm32_usbphyc_get_pll_params(clk_rate, &pll_params); + + usbphyc_pll = PLLDITHEN1 | PLLDITHEN0 | PLLSTRBYP; + usbphyc_pll |= ((pll_params.ndiv << PLLNDIV_SHIFT) & PLLNDIV); + + if (pll_params.frac) { + usbphyc_pll |= PLLFRACCTL; + usbphyc_pll |= ((pll_params.frac << PLLFRACIN_SHIFT) + & PLLFRACIN); + } + + writel(usbphyc_pll, usbphyc->base + STM32_USBPHYC_PLL); + + pr_debug("%s: input clk freq=%dHz, ndiv=%d, frac=%d\n", __func__, + clk_rate, pll_params.ndiv, pll_params.frac); + + return 0; +} + +static bool stm32_usbphyc_is_init(struct stm32_usbphyc *usbphyc) +{ + int i; + + for (i = 0; i < MAX_PHYS; i++) { + if (usbphyc->phys[i].init) + return true; + } + + return false; +} + +static bool stm32_usbphyc_is_powered(struct stm32_usbphyc *usbphyc) +{ + int i; + + for (i = 0; i < MAX_PHYS; i++) { + if (usbphyc->phys[i].powered) + return true; + } + + return false; +} + +static int stm32_usbphyc_phy_init(struct phy *phy) +{ + struct stm32_usbphyc *usbphyc = dev_get_priv(phy->dev); + struct stm32_usbphyc_phy *usbphyc_phy = usbphyc->phys + phy->id; + bool pllen = readl(usbphyc->base + STM32_USBPHYC_PLL) & PLLEN ? + true : false; + int ret; + + pr_debug("%s phy ID = %lu\n", __func__, phy->id); + /* Check if one phy port has already configured the pll */ + if (pllen && stm32_usbphyc_is_init(usbphyc)) + goto initialized; + + if (pllen) { + clrbits_le32(usbphyc->base + STM32_USBPHYC_PLL, PLLEN); + udelay(PLL_PWR_DOWN_TIME_US); + } + + ret = stm32_usbphyc_pll_init(usbphyc); + if (ret) + return ret; + + setbits_le32(usbphyc->base + STM32_USBPHYC_PLL, PLLEN); + + /* + * We must wait PLL_LOCK_TIME_US before checking that PLLEN + * bit is still set + */ + udelay(PLL_LOCK_TIME_US); + + if (!(readl(usbphyc->base + STM32_USBPHYC_PLL) & PLLEN)) + return -EIO; + +initialized: + usbphyc_phy->init = true; + + return 0; +} + +static int stm32_usbphyc_phy_exit(struct phy *phy) +{ + struct stm32_usbphyc *usbphyc = dev_get_priv(phy->dev); + struct stm32_usbphyc_phy *usbphyc_phy = usbphyc->phys + phy->id; + + pr_debug("%s phy ID = %lu\n", __func__, phy->id); + usbphyc_phy->init = false; + + /* Check if other phy port requires pllen */ + if (stm32_usbphyc_is_init(usbphyc)) + return 0; + + clrbits_le32(usbphyc->base + STM32_USBPHYC_PLL, PLLEN); + + /* + * We must wait PLL_PWR_DOWN_TIME_US before checking that PLLEN + * bit is still clear + */ + udelay(PLL_PWR_DOWN_TIME_US); + + if (readl(usbphyc->base + STM32_USBPHYC_PLL) & PLLEN) + return -EIO; + + return 0; +} + +static int stm32_usbphyc_phy_power_on(struct phy *phy) +{ + struct stm32_usbphyc *usbphyc = dev_get_priv(phy->dev); + struct stm32_usbphyc_phy *usbphyc_phy = usbphyc->phys + phy->id; + int ret; + + pr_debug("%s phy ID = %lu\n", __func__, phy->id); + if (usbphyc_phy->vdda1v1) { + ret = regulator_set_enable(usbphyc_phy->vdda1v1, true); + if (ret) + return ret; + } + + if (usbphyc_phy->vdda1v8) { + ret = regulator_set_enable(usbphyc_phy->vdda1v8, true); + if (ret) + return ret; + } + if (usbphyc_phy->vdd) { + ret = regulator_set_enable(usbphyc_phy->vdd, true); + if (ret) + return ret; + } + + usbphyc_phy->powered = true; + + return 0; +} + +static int stm32_usbphyc_phy_power_off(struct phy *phy) +{ + struct stm32_usbphyc *usbphyc = dev_get_priv(phy->dev); + struct stm32_usbphyc_phy *usbphyc_phy = usbphyc->phys + phy->id; + int ret; + + pr_debug("%s phy ID = %lu\n", __func__, phy->id); + usbphyc_phy->powered = false; + + if (stm32_usbphyc_is_powered(usbphyc)) + return 0; + + if (usbphyc_phy->vdda1v1) { + ret = regulator_set_enable(usbphyc_phy->vdda1v1, false); + if (ret) + return ret; + } + + if (usbphyc_phy->vdda1v8) { + ret = regulator_set_enable(usbphyc_phy->vdda1v8, false); + if (ret) + return ret; + } + + if (usbphyc_phy->vdd) { + ret = regulator_set_enable(usbphyc_phy->vdd, false); + if (ret) + return ret; + } + + return 0; +} + +static int stm32_usbphyc_get_regulator(struct udevice *dev, ofnode node, + char *supply_name, + struct udevice **regulator) +{ + struct ofnode_phandle_args regulator_phandle; + int ret; + + ret = ofnode_parse_phandle_with_args(node, supply_name, + NULL, 0, 0, + ®ulator_phandle); + if (ret) { + dev_err(dev, "Can't find %s property (%d)\n", supply_name, ret); + return ret; + } + + ret = uclass_get_device_by_ofnode(UCLASS_REGULATOR, + regulator_phandle.node, + regulator); + + if (ret) { + dev_err(dev, "Can't get %s regulator (%d)\n", supply_name, ret); + return ret; + } + + return 0; +} + +static int stm32_usbphyc_of_xlate(struct phy *phy, + struct ofnode_phandle_args *args) +{ + if (args->args_count > 1) { + pr_debug("%s: invalid args_count: %d\n", __func__, + args->args_count); + return -EINVAL; + } + + if (args->args[0] >= MAX_PHYS) + return -ENODEV; + + if (args->args_count) + phy->id = args->args[0]; + else + phy->id = 0; + + return 0; +} + +static const struct phy_ops stm32_usbphyc_phy_ops = { + .init = stm32_usbphyc_phy_init, + .exit = stm32_usbphyc_phy_exit, + .power_on = stm32_usbphyc_phy_power_on, + .power_off = stm32_usbphyc_phy_power_off, + .of_xlate = stm32_usbphyc_of_xlate, +}; + +static int stm32_usbphyc_probe(struct udevice *dev) +{ + struct stm32_usbphyc *usbphyc = dev_get_priv(dev); + struct reset_ctl reset; + ofnode node; + int i, ret; + + usbphyc->base = dev_read_addr(dev); + if (usbphyc->base == FDT_ADDR_T_NONE) + return -EINVAL; + + /* Enable clock */ + ret = clk_get_by_index(dev, 0, &usbphyc->clk); + if (ret) + return ret; + + ret = clk_enable(&usbphyc->clk); + if (ret) + return ret; + + /* Reset */ + ret = reset_get_by_index(dev, 0, &reset); + if (!ret) { + reset_assert(&reset); + udelay(2); + reset_deassert(&reset); + } + + /* + * parse all PHY subnodes in order to populate regulator associated + * to each PHY port + */ + node = dev_read_first_subnode(dev); + for (i = 0; i < MAX_PHYS; i++) { + struct stm32_usbphyc_phy *usbphyc_phy = usbphyc->phys + i; + + usbphyc_phy->index = i; + usbphyc_phy->init = false; + usbphyc_phy->powered = false; + ret = stm32_usbphyc_get_regulator(dev, node, "phy-supply", + &usbphyc_phy->vdd); + if (ret) + return ret; + + ret = stm32_usbphyc_get_regulator(dev, node, "vdda1v1-supply", + &usbphyc_phy->vdda1v1); + if (ret) + return ret; + + ret = stm32_usbphyc_get_regulator(dev, node, "vdda1v8-supply", + &usbphyc_phy->vdda1v8); + if (ret) + return ret; + + node = dev_read_next_subnode(node); + } + + /* Check if second port has to be used for host controller */ + if (dev_read_bool(dev, "st,port2-switch-to-host")) + setbits_le32(usbphyc->base + STM32_USBPHYC_MISC, SWITHOST); + + return 0; +} + +static const struct udevice_id stm32_usbphyc_of_match[] = { + { .compatible = "st,stm32mp1-usbphyc", }, + { }, +}; + +U_BOOT_DRIVER(stm32_usb_phyc) = { + .name = "stm32-usbphyc", + .id = UCLASS_PHY, + .of_match = stm32_usbphyc_of_match, + .ops = &stm32_usbphyc_phy_ops, + .probe = stm32_usbphyc_probe, + .priv_auto_alloc_size = sizeof(struct stm32_usbphyc), +}; |