Hi, On 22/11/2019 07:55, Hanjie Lin wrote: > Adds support for Amlogic A1 USB Control Glue HW. > > The Amlogic A1 SoC Family embeds 1 USB Controllers: > - a DWC3 IP configured as Host for USB2 and USB3 > > A glue connects the controllers to the USB2 PHY of A1 SoC. > > Signed-off-by: Hanjie Lin <hanjie.lin@xxxxxxxxxxx> > Signed-off-by: Yue Wang <yue.wang@xxxxxxxxxxx> > --- > drivers/usb/dwc3/Kconfig | 11 ++ > drivers/usb/dwc3/Makefile | 1 + > drivers/usb/dwc3/dwc3-meson-a1.c | 397 +++++++++++++++++++++++++++++++++++++++ > 3 files changed, 409 insertions(+) > create mode 100644 drivers/usb/dwc3/dwc3-meson-a1.c > > diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig > index 556a876..9bfb159 100644 > --- a/drivers/usb/dwc3/Kconfig > +++ b/drivers/usb/dwc3/Kconfig > @@ -96,6 +96,17 @@ config USB_DWC3_KEYSTONE > Support of USB2/3 functionality in TI Keystone2 and AM654 platforms. > Say 'Y' or 'M' here if you have one such device > > +config USB_DWC3_MESON_A1 > + tristate "Amlogic Meson A1 Platforms" > + depends on OF && COMMON_CLK > + depends on ARCH_MESON || COMPILE_TEST > + default USB_DWC3 > + help > + Support USB2 functionality in MESON A1 platforms. > + The MESON A1 USB2 support a DWC3 USB IP Core configured for USB2 in > + host-only mode. > + Say 'Y' or 'M' if you have one such device. > + > config USB_DWC3_MESON_G12A > tristate "Amlogic Meson G12A Platforms" > depends on OF && COMMON_CLK > diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile > index ae86da0..a3fc655 100644 > --- a/drivers/usb/dwc3/Makefile > +++ b/drivers/usb/dwc3/Makefile > @@ -47,6 +47,7 @@ obj-$(CONFIG_USB_DWC3_EXYNOS) += dwc3-exynos.o > obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o > obj-$(CONFIG_USB_DWC3_HAPS) += dwc3-haps.o > obj-$(CONFIG_USB_DWC3_KEYSTONE) += dwc3-keystone.o > +obj-$(CONFIG_USB_DWC3_MESON_A1) += dwc3-meson-a1.o > obj-$(CONFIG_USB_DWC3_MESON_G12A) += dwc3-meson-g12a.o > obj-$(CONFIG_USB_DWC3_OF_SIMPLE) += dwc3-of-simple.o > obj-$(CONFIG_USB_DWC3_ST) += dwc3-st.o > diff --git a/drivers/usb/dwc3/dwc3-meson-a1.c b/drivers/usb/dwc3/dwc3-meson-a1.c > new file mode 100644 > index 00000000..db2b99a > --- /dev/null > +++ b/drivers/usb/dwc3/dwc3-meson-a1.c > @@ -0,0 +1,397 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * USB Glue for Amlogic A1 SoCs > + * > + * Copyright (c) 2019 Amlogic, Inc. All rights reserved > + * Author: Yue Wang <yue.wang@xxxxxxxxxxx> > + */ > + > +/* > + * The USB is organized with a glue around the DWC3 Controller IP as : > + * - Control registers for each USB2 Ports > + * - Control registers for the USB PHY layer > + */ > + > +#include <linux/module.h> > +#include <linux/kernel.h> > +#include <linux/platform_device.h> > +#include <linux/clk.h> > +#include <linux/of.h> > +#include <linux/of_platform.h> > +#include <linux/pm_domain.h> > +#include <linux/pm_runtime.h> > +#include <linux/regmap.h> > +#include <linux/bitfield.h> > +#include <linux/bitops.h> > +#include <linux/reset.h> > +#include <linux/phy/phy.h> > +#include <linux/usb/otg.h> > +#include <linux/usb/role.h> > +#include <linux/regulator/consumer.h> > + > +/* USB2 Ports Control Registers */ > +#define U2P_R0 0x20 > + #define U2P_R0_HOST_DEVICE BIT(0) > + #define U2P_R0_POWER_OK BIT(1) > + #define U2P_R0_HAST_MODE BIT(2) > + #define U2P_R0_POWER_ON_RESET BIT(3) > + #define U2P_R0_ID_PULLUP BIT(4) > + #define U2P_R0_DRV_VBUS BIT(5) > + > +#define U2P_R1 0x24 > + #define U2P_R1_PHY_READY BIT(0) > + #define U2P_R1_ID_DIG BIT(1) > + #define U2P_R1_OTG_SESSION_VALID BIT(2) > + #define U2P_R1_VBUS_VALID BIT(3) > + > +/* USB Glue Control Registers */ > + > +#define USB_R0 0x80 > + #define USB_R0_P30_LANE0_TX2RX_LOOPBACK BIT(17) > + #define USB_R0_P30_LANE0_EXT_PCLK_REQ BIT(18) > + #define USB_R0_P30_PCS_RX_LOS_MASK_VAL_MASK GENMASK(28, 19) > + #define USB_R0_U2D_SS_SCALEDOWN_MODE_MASK GENMASK(30, 29) > + #define USB_R0_U2D_ACT BIT(31) > + > +#define USB_R1 0x84 > + #define USB_R1_U3H_BIGENDIAN_GS BIT(0) > + #define USB_R1_U3H_PME_ENABLE BIT(1) > + #define USB_R1_U3H_HUB_PORT_OVERCURRENT_MASK GENMASK(4, 2) > + #define USB_R1_U3H_HUB_PORT_PERM_ATTACH_MASK GENMASK(9, 7) > + #define USB_R1_U3H_HOST_U2_PORT_DISABLE_MASK GENMASK(13, 12) > + #define USB_R1_U3H_HOST_U3_PORT_DISABLE BIT(16) > + #define USB_R1_U3H_HOST_PORT_POWER_CONTROL_PRESENT BIT(17) > + #define USB_R1_U3H_HOST_MSI_ENABLE BIT(18) > + #define USB_R1_U3H_FLADJ_30MHZ_REG_MASK GENMASK(24, 19) > + #define USB_R1_P30_PCS_TX_SWING_FULL_MASK GENMASK(31, 25) > + > +#define USB_R2 0x88 > + #define USB_R2_P30_PCS_TX_DEEMPH_3P5DB_MASK GENMASK(25, 20) > + #define USB_R2_P30_PCS_TX_DEEMPH_6DB_MASK GENMASK(31, 26) > + > +#define USB_R3 0x8c > + #define USB_R3_P30_SSC_ENABLE BIT(0) > + #define USB_R3_P30_SSC_RANGE_MASK GENMASK(3, 1) > + #define USB_R3_P30_SSC_REF_CLK_SEL_MASK GENMASK(12, 4) > + #define USB_R3_P30_REF_SSP_EN BIT(13) > + > +#define USB_R4 0x90 > + #define USB_R4_P21_PORT_RESET_0 BIT(0) > + #define USB_R4_P21_SLEEP_M0 BIT(1) > + #define USB_R4_MEM_PD_MASK GENMASK(3, 2) > + #define USB_R4_P21_ONLY BIT(4) > + > +#define USB_R5 0x94 > + #define USB_R5_ID_DIG_SYNC BIT(0) > + #define USB_R5_ID_DIG_REG BIT(1) > + #define USB_R5_ID_DIG_CFG_MASK GENMASK(3, 2) > + #define USB_R5_ID_DIG_EN_0 BIT(4) > + #define USB_R5_ID_DIG_EN_1 BIT(5) > + #define USB_R5_ID_DIG_CURR BIT(6) > + #define USB_R5_ID_DIG_IRQ BIT(7) > + #define USB_R5_ID_DIG_TH_MASK GENMASK(15, 8) > + #define USB_R5_ID_DIG_CNT_MASK GENMASK(23, 16) > + > +static const char *phy_names = { > + "usb2-phy0", > +}; > + > +struct dwc3_meson_a1 { > + struct device *dev; > + struct regmap *regmap; > + struct clk *clk_usb_ctrl; > + struct clk *clk_usb_bus; > + struct clk *clk_xtal_usb_phy; > + struct clk *clk_xtal_usb_ctrl; > + struct reset_control *reset; > + struct phy *phys; > + unsigned int usb2_ports; > +}; > + > +static void dwc3_meson_a1_usb_init(struct dwc3_meson_a1 *priv) > +{ > + regmap_update_bits(priv->regmap, U2P_R0, > + U2P_R0_POWER_ON_RESET, > + U2P_R0_POWER_ON_RESET); > + > + regmap_update_bits(priv->regmap, U2P_R0, > + U2P_R0_HOST_DEVICE, > + U2P_R0_HOST_DEVICE); > + > + regmap_update_bits(priv->regmap, U2P_R0, > + U2P_R0_POWER_ON_RESET, 0); > + > + regmap_update_bits(priv->regmap, USB_R1, > + USB_R1_U3H_FLADJ_30MHZ_REG_MASK, > + FIELD_PREP(USB_R1_U3H_FLADJ_30MHZ_REG_MASK, 0x20)); > + > + regmap_update_bits(priv->regmap, USB_R0, > + USB_R0_U2D_ACT, 0); > + > + regmap_update_bits(priv->regmap, USB_R4, > + USB_R4_P21_SLEEP_M0, 0); > +} > + > +static const struct regmap_config phy_meson_a1_usb_regmap_conf = { > + .reg_bits = 32, > + .val_bits = 32, > + .reg_stride = 4, > + .max_register = USB_R5, > +}; > + > +static int dwc3_meson_a1_get_phys(struct dwc3_meson_a1 *priv) > +{ > + priv->phys = devm_phy_optional_get(priv->dev, phy_names); > + if (IS_ERR(priv->phys)) > + return PTR_ERR(priv->phys); > + > + priv->usb2_ports++; > + > + dev_info(priv->dev, "USB2 ports: %d\n", priv->usb2_ports); > + > + return 0; > +} > + > +static int dwc3_meson_a1_enable_clk(struct dwc3_meson_a1 *priv) > +{ > + int ret; > + > + ret = clk_prepare_enable(priv->clk_usb_ctrl); > + if (ret < 0) { > + dev_err(priv->dev, "can't enable usb_ctrl clock.\n"); > + return ret; > + } > + > + ret = clk_prepare_enable(priv->clk_usb_bus); > + if (ret < 0) { > + dev_err(priv->dev, "can't enable usb_bus clock.\n"); > + goto disable_clk_usb_ctrl; > + } > + > + ret = clk_prepare_enable(priv->clk_xtal_usb_phy); > + if (ret < 0) { > + dev_err(priv->dev, "can't enable xtal_usb_phy clock.\n"); > + goto disable_clk_usb_bus; > + } > + > + ret = clk_prepare_enable(priv->clk_xtal_usb_ctrl); > + if (ret < 0) { > + dev_err(priv->dev, "can't enable xtal_usb_ctrl clock.\n"); > + goto disable_clk_xtal_usb_phy; > + } > + > + return 0; > + > +disable_clk_xtal_usb_phy: > + clk_disable_unprepare(priv->clk_xtal_usb_phy); > +disable_clk_usb_bus: > + clk_disable_unprepare(priv->clk_usb_bus); > +disable_clk_usb_ctrl: > + clk_disable_unprepare(priv->clk_usb_ctrl); > + > + return ret; > +} > + > +static void dwc3_meson_a1_disable_clk(struct dwc3_meson_a1 *priv) > +{ > + clk_disable_unprepare(priv->clk_usb_ctrl); > + clk_disable_unprepare(priv->clk_usb_bus); > + clk_disable_unprepare(priv->clk_xtal_usb_phy); > + clk_disable_unprepare(priv->clk_xtal_usb_ctrl); > +} > + > +static int dwc3_meson_a1_setup_clk(struct dwc3_meson_a1 *priv) > +{ > + int ret; > + > + priv->clk_usb_ctrl = devm_clk_get(priv->dev, "usb_ctrl"); > + if (IS_ERR(priv->clk_usb_ctrl)) { > + dev_err(priv->dev, "can't get usb_ctrl clock.\n"); > + return PTR_ERR(priv->clk_usb_ctrl); > + } > + > + priv->clk_usb_bus = devm_clk_get(priv->dev, "usb_bus"); > + if (IS_ERR(priv->clk_usb_bus)) { > + dev_err(priv->dev, "can't get usb_bus clock.\n"); > + return PTR_ERR(priv->clk_usb_bus); > + } > + > + priv->clk_xtal_usb_phy = devm_clk_get(priv->dev, "xtal_usb_phy"); > + if (IS_ERR(priv->clk_xtal_usb_phy)) { > + dev_err(priv->dev, "can't get xtal_usb_phy clock.\n"); > + return PTR_ERR(priv->clk_xtal_usb_phy); > + } > + > + priv->clk_xtal_usb_ctrl = devm_clk_get(priv->dev, "xtal_usb_ctrl"); > + if (IS_ERR(priv->clk_xtal_usb_ctrl)) { > + dev_err(priv->dev, "can't get xtal_usb_ctrl clock.\n"); > + return PTR_ERR(priv->clk_xtal_usb_ctrl); > + } > + > + ret = dwc3_meson_a1_enable_clk(priv); > + if (ret) > + return ret; > + > + devm_add_action_or_reset(priv->dev, > + (void(*)(void *))clk_disable_unprepare, > + priv->clk_usb_ctrl); > + devm_add_action_or_reset(priv->dev, > + (void(*)(void *))clk_disable_unprepare, > + priv->clk_usb_bus); > + devm_add_action_or_reset(priv->dev, > + (void(*)(void *))clk_disable_unprepare, > + priv->clk_xtal_usb_phy); > + devm_add_action_or_reset(priv->dev, > + (void(*)(void *))clk_disable_unprepare, > + priv->clk_xtal_usb_ctrl); > + > + return 0; > +} > + > +static int dwc3_meson_a1_probe(struct platform_device *pdev) > +{ > + struct dwc3_meson_a1 *priv; > + struct device *dev = &pdev->dev; > + struct device_node *np = dev->of_node; > + void __iomem *base; > + int ret; > + > + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + platform_set_drvdata(pdev, priv); > + priv->dev = dev; > + > + base = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(base)) > + return PTR_ERR(base); > + > + priv->regmap = devm_regmap_init_mmio(dev, base, > + &phy_meson_a1_usb_regmap_conf); > + if (IS_ERR(priv->regmap)) > + return PTR_ERR(priv->regmap); > + > + ret = dwc3_meson_a1_setup_clk(priv); > + if (ret) > + return ret; > + > + priv->reset = devm_reset_control_get(dev, NULL); > + if (IS_ERR(priv->reset)) { > + ret = PTR_ERR(priv->reset); > + dev_err(dev, "failed to get device reset, err=%d\n", ret); > + return ret; > + } > + > + ret = reset_control_reset(priv->reset); > + if (ret) > + return ret; > + > + ret = dwc3_meson_a1_get_phys(priv); > + if (ret) > + return ret; > + > + dwc3_meson_a1_usb_init(priv); > + > + /* Init PHYs */ > + ret = phy_init(priv->phys); > + if (ret) > + return ret; > + > + /* Set PHY Power */ > + ret = phy_power_on(priv->phys); > + if (ret) > + goto err_phys_exit; > + > + ret = of_platform_populate(np, NULL, NULL, dev); > + if (ret) > + goto err_phys_power; > + > + return 0; > + > +err_phys_power: > + phy_power_off(priv->phys); > + > +err_phys_exit: > + phy_exit(priv->phys); > + > + return ret; > +} > + > +static int dwc3_meson_a1_remove(struct platform_device *pdev) > +{ > + struct dwc3_meson_a1 *priv = platform_get_drvdata(pdev); > + struct device *dev = &pdev->dev; > + > + of_platform_depopulate(dev); > + > + phy_power_off(priv->phys); > + phy_exit(priv->phys); > + > + return 0; > +} > + > +static int __maybe_unused dwc3_meson_a1_suspend(struct device *dev) > +{ > + struct dwc3_meson_a1 *priv = dev_get_drvdata(dev); > + > + phy_power_off(priv->phys); > + phy_exit(priv->phys); > + > + reset_control_assert(priv->reset); > + > + dwc3_meson_a1_disable_clk(priv); > + > + return 0; > +} > + > +static int __maybe_unused dwc3_meson_a1_resume(struct device *dev) > +{ > + struct dwc3_meson_a1 *priv = dev_get_drvdata(dev); > + int ret; > + > + ret = dwc3_meson_a1_enable_clk(priv); > + if (ret) > + return ret; > + > + reset_control_deassert(priv->reset); > + > + dwc3_meson_a1_usb_init(priv); > + > + /* Init PHYs */ > + ret = phy_init(priv->phys); > + if (ret) > + return ret; > + > + /* Set PHY Power */ > + ret = phy_power_on(priv->phys); > + if (ret) > + return ret; > + > + return 0; > +} > + > +static const struct dev_pm_ops dwc3_meson_a1_dev_pm_ops = { > + SET_SYSTEM_SLEEP_PM_OPS(dwc3_meson_a1_suspend, dwc3_meson_a1_resume) > +}; > + > +static const struct of_device_id dwc3_meson_a1_match[] = { > + { .compatible = "amlogic,meson-a1-usb-ctrl" }, > + { /* Sentinel */ } > +}; > +MODULE_DEVICE_TABLE(of, dwc3_meson_a1_match); > + > +static struct platform_driver dwc3_meson_a1_driver = { > + .probe = dwc3_meson_a1_probe, > + .remove = dwc3_meson_a1_remove, > + .driver = { > + .name = "dwc3-meson-a1", > + .of_match_table = dwc3_meson_a1_match, > + .pm = &dwc3_meson_a1_dev_pm_ops, > + }, > +}; > + > +module_platform_driver(dwc3_meson_a1_driver); > +MODULE_LICENSE("GPL v2"); > +MODULE_DESCRIPTION("Amlogic Meson A1 USB Glue Layer"); > +MODULE_AUTHOR("Yue Wang <yue.wang@xxxxxxxxxxx>"); > This driver looks very close to the g12a glue driver, could you reuse the g12a driver instead ? Neil