phy: add support for STM32 usb phy controller
This patch adds phy tranceiver driver for STM32 USB PHY Controller (usbphyc) that provides dual port High-Speed phy for OTG (single port) and EHCI/OHCI host controller (two ports). One port of the phy is shared between the two USB controllers through a UTMI+ switch. Signed-off-by: Christophe Kerello <christophe.kerello@st.com> Signed-off-by: Amelie Delaunay <amelie.delaunay@st.com> Signed-off-by: Patrice Chotard <patrice.chotard@st.com>
This commit is contained in:
parent
1fe9ae76b1
commit
3b29121678
73
Documentation/devicetree/bindings/phy/phy-stm32-usbphyc.txt
Normal file
73
Documentation/devicetree/bindings/phy/phy-stm32-usbphyc.txt
Normal file
@ -0,0 +1,73 @@
|
||||
STMicroelectronics STM32 USB HS PHY controller
|
||||
|
||||
The STM32 USBPHYC block contains a dual port High Speed UTMI+ PHY and a UTMI
|
||||
switch. It controls PHY configuration and status, and the UTMI+ switch that
|
||||
selects either OTG or HOST controller for the second PHY port. It also sets
|
||||
PLL configuration.
|
||||
|
||||
USBPHYC
|
||||
|_ PLL
|
||||
|
|
||||
|_ PHY port#1 _________________ HOST controller
|
||||
| _ |
|
||||
| / 1|________________|
|
||||
|_ PHY port#2 ----| |________________
|
||||
| \_0| |
|
||||
|_ UTMI switch_______| OTG controller
|
||||
|
||||
|
||||
Phy provider node
|
||||
=================
|
||||
|
||||
Required properties:
|
||||
- compatible: must be "st,stm32mp1-usbphyc"
|
||||
- reg: address and length of the usb phy control register set
|
||||
- clocks: phandle + clock specifier for the PLL phy clock
|
||||
- #address-cells: number of address cells for phys sub-nodes, must be <1>
|
||||
- #size-cells: number of size cells for phys sub-nodes, must be <0>
|
||||
|
||||
Optional properties:
|
||||
- assigned-clocks: phandle + clock specifier for the PLL phy clock
|
||||
- assigned-clock-parents: the PLL phy clock parent
|
||||
- resets: phandle + reset specifier
|
||||
|
||||
Required nodes: one sub-node per port the controller provides.
|
||||
|
||||
Phy sub-nodes
|
||||
==============
|
||||
|
||||
Required properties:
|
||||
- reg: phy port index
|
||||
- phy-supply: phandle to the regulator providing 3V3 power to the PHY,
|
||||
see phy-bindings.txt in the same directory.
|
||||
- vdda1v1-supply: phandle to the regulator providing 1V1 power to the PHY
|
||||
- vdda1v8-supply: phandle to the regulator providing 1V8 power to the PHY
|
||||
- #phy-cells: see phy-bindings.txt in the same directory, must be <0> for PHY
|
||||
port#1 and must be <1> for PHY port#2, to select USB controller
|
||||
|
||||
|
||||
Example:
|
||||
usbphyc: usb-phy@5a006000 {
|
||||
compatible = "st,stm32mp1-usbphyc";
|
||||
reg = <0x5a006000 0x1000>;
|
||||
clocks = <&rcc_clk USBPHY_K>;
|
||||
resets = <&rcc_rst USBPHY_R>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
usbphyc_port0: usb-phy@0 {
|
||||
reg = <0>;
|
||||
phy-supply = <&vdd_usb>;
|
||||
vdda1v1-supply = <®11>;
|
||||
vdda1v8-supply = <®18>
|
||||
#phy-cells = <0>;
|
||||
};
|
||||
|
||||
usbphyc_port1: usb-phy@1 {
|
||||
reg = <1>;
|
||||
phy-supply = <&vdd_usb>;
|
||||
vdda1v1-supply = <®11>;
|
||||
vdda1v8-supply = <®18>
|
||||
#phy-cells = <1>;
|
||||
};
|
||||
};
|
@ -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
|
||||
|
@ -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
|
||||
|
402
drivers/phy/phy-stm32-usbphyc.c
Normal file
402
drivers/phy/phy-stm32-usbphyc.c
Normal file
@ -0,0 +1,402 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+ 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),
|
||||
};
|
Loading…
Reference in New Issue
Block a user