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:
Patrice Chotard 2018-04-27 11:01:55 +02:00 committed by Marek Vasut
parent 1fe9ae76b1
commit 3b29121678
4 changed files with 489 additions and 0 deletions

View 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 = <&reg11>;
vdda1v8-supply = <&reg18>
#phy-cells = <0>;
};
usbphyc_port1: usb-phy@1 {
reg = <1>;
phy-supply = <&vdd_usb>;
vdda1v1-supply = <&reg11>;
vdda1v8-supply = <&reg18>
#phy-cells = <1>;
};
};

View File

@ -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

View File

@ -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

View 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,
&regulator_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),
};