From patchwork Thu Apr 26 14:23:17 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Patrice CHOTARD X-Patchwork-Id: 905092 X-Patchwork-Delegate: marek.vasut@gmail.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=lists.denx.de (client-ip=81.169.180.215; helo=lists.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=st.com Received: from lists.denx.de (dione.denx.de [81.169.180.215]) by ozlabs.org (Postfix) with ESMTP id 40Wznq3zMsz9s02 for ; Fri, 27 Apr 2018 00:23:41 +1000 (AEST) Received: by lists.denx.de (Postfix, from userid 105) id 70BF5C220CF; Thu, 26 Apr 2018 14:23:38 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on lists.denx.de X-Spam-Level: X-Spam-Status: No, score=0.0 required=5.0 tests=RCVD_IN_DNSWL_BLOCKED autolearn=unavailable autolearn_force=no version=3.4.0 Received: from lists.denx.de (localhost [IPv6:::1]) by lists.denx.de (Postfix) with ESMTP id 3EC1BC2204A; Thu, 26 Apr 2018 14:23:34 +0000 (UTC) Received: by lists.denx.de (Postfix, from userid 105) id D866EC2204A; Thu, 26 Apr 2018 14:23:31 +0000 (UTC) Received: from mx07-00178001.pphosted.com (mx08-00178001.pphosted.com [91.207.212.93]) by lists.denx.de (Postfix) with ESMTPS id 83881C22047 for ; Thu, 26 Apr 2018 14:23:31 +0000 (UTC) Received: from pps.filterd (m0046660.ppops.net [127.0.0.1]) by mx08-.pphosted.com (8.16.0.21/8.16.0.21) with SMTP id w3QEJpET022883; Thu, 26 Apr 2018 16:23:29 +0200 Received: from beta.dmz-eu.st.com (beta.dmz-eu.st.com [164.129.1.35]) by mx08-00178001.pphosted.com with ESMTP id 2hfte9bpnp-1 (version=TLSv1 cipher=ECDHE-RSA-AES256-SHA bits=256 verify=NOT); Thu, 26 Apr 2018 16:23:29 +0200 Received: from zeta.dmz-eu.st.com (zeta.dmz-eu.st.com [164.129.230.9]) by beta.dmz-eu.st.com (STMicroelectronics) with ESMTP id 2305B34; Thu, 26 Apr 2018 14:23:28 +0000 (GMT) Received: from Webmail-eu.st.com (sfhdag6node3.st.com [10.75.127.18]) by zeta.dmz-eu.st.com (STMicroelectronics) with ESMTP id D2E104E0C; Thu, 26 Apr 2018 14:23:27 +0000 (GMT) Received: from localhost (10.75.127.51) by SFHDAG6NODE3.st.com (10.75.127.18) with Microsoft SMTP Server (TLS) id 15.0.1347.2; Thu, 26 Apr 2018 16:23:27 +0200 From: Patrice Chotard To: Date: Thu, 26 Apr 2018 16:23:17 +0200 Message-ID: <1524752597-26294-1-git-send-email-patrice.chotard@st.com> X-Mailer: git-send-email 1.9.1 MIME-Version: 1.0 X-Originating-IP: [10.75.127.51] X-ClientProxiedBy: SFHDAG2NODE3.st.com (10.75.127.6) To SFHDAG6NODE3.st.com (10.75.127.18) X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10434:, , definitions=2018-04-26_05:, , signatures=0 Cc: Marek Vasut , Joe Hershberger Subject: [U-Boot] [RESEND][PATCH v1] phy: add support for STM32 usb phy controller X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.18 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" From: Christophe Kerello 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 Signed-off-by: Patrice Chotard --- drivers/phy/Kconfig | 13 ++ drivers/phy/Makefile | 1 + drivers/phy/phy-stm32-usbphyc.c | 403 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 417 insertions(+) create mode 100644 drivers/phy/phy-stm32-usbphyc.c diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 4e9d09910c32..1de3f31bcdd1 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -110,4 +110,17 @@ 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. + endmenu diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 68087ae3b134..e93c3257230d 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -14,3 +14,4 @@ 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 diff --git a/drivers/phy/phy-stm32-usbphyc.c b/drivers/phy/phy-stm32-usbphyc.c new file mode 100644 index 000000000000..744a26c4cd3d --- /dev/null +++ b/drivers/phy/phy-stm32-usbphyc.c @@ -0,0 +1,403 @@ +/* + * Copyright (C) 2018, STMicroelectronics - All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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), +};