| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Amlogic G12A DWC3 Glue layer |
| * |
| * Copyright (C) 2019 BayLibre, SAS |
| * Author: Neil Armstrong <narmstrong@baylibre.com> |
| */ |
| |
| #include <log.h> |
| #include <dm.h> |
| #include <dm/device-internal.h> |
| #include <dm/lists.h> |
| #include <dwc3-uboot.h> |
| #include <generic-phy.h> |
| #include <linux/delay.h> |
| #include <linux/io.h> |
| #include <linux/printk.h> |
| #include <linux/usb/ch9.h> |
| #include <linux/usb/gadget.h> |
| #include <malloc.h> |
| #include <regmap.h> |
| #include <usb.h> |
| #include "core.h" |
| #include "gadget.h" |
| #include <reset.h> |
| #include <clk.h> |
| #include <power/regulator.h> |
| #include <linux/bitfield.h> |
| #include <linux/bitops.h> |
| #include <linux/compat.h> |
| #include <dt-bindings/clock/amlogic,a1-peripherals-clkc.h> |
| |
| /* USB2 Ports Control Registers */ |
| |
| #define U2P_REG_SIZE 0x20 |
| |
| #define U2P_R0 0x0 |
| #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 0x4 |
| #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) |
| |
| enum { |
| USB2_HOST_PHY = 0, |
| USB2_OTG_PHY, |
| USB3_HOST_PHY, |
| PHY_COUNT, |
| }; |
| |
| static const char *const dwc3_meson_g12a_phy_names[] = { |
| "usb2-phy0", "usb2-phy1", "usb3-phy0", |
| }; |
| |
| static const char *const dwc3_meson_a1_phy_names[] = { |
| "usb2-phy0", "usb2-phy1" |
| }; |
| |
| struct dwc3_meson_g12a; |
| |
| struct dwc3_meson_g12a_drvdata { |
| const char *const *phy_names; |
| unsigned int phy_cnt; |
| int (*clk_init)(struct dwc3_meson_g12a *priv); |
| }; |
| |
| struct dwc3_meson_g12a { |
| struct udevice *dev; |
| struct regmap *regmap; |
| struct clk clk; |
| struct reset_ctl reset; |
| struct phy phys[PHY_COUNT]; |
| enum usb_dr_mode otg_mode; |
| enum usb_dr_mode otg_phy_mode; |
| unsigned int usb2_ports; |
| unsigned int usb3_ports; |
| #if CONFIG_IS_ENABLED(DM_REGULATOR) |
| struct udevice *vbus_supply; |
| #endif |
| struct dwc3_meson_g12a_drvdata *drvdata; |
| }; |
| |
| #define U2P_REG_SIZE 0x20 |
| #define USB_REG_OFFSET 0x80 |
| |
| static void dwc3_meson_g12a_usb2_set_mode(struct dwc3_meson_g12a *priv, |
| int i, enum usb_dr_mode mode) |
| { |
| switch (mode) { |
| case USB_DR_MODE_HOST: |
| case USB_DR_MODE_OTG: |
| case USB_DR_MODE_UNKNOWN: |
| regmap_update_bits(priv->regmap, U2P_R0 + (U2P_REG_SIZE * i), |
| U2P_R0_HOST_DEVICE, |
| U2P_R0_HOST_DEVICE); |
| break; |
| |
| case USB_DR_MODE_PERIPHERAL: |
| regmap_update_bits(priv->regmap, U2P_R0 + (U2P_REG_SIZE * i), |
| U2P_R0_HOST_DEVICE, 0); |
| break; |
| } |
| } |
| |
| static int dwc3_meson_g12a_usb2_init(struct dwc3_meson_g12a *priv) |
| { |
| int i; |
| |
| if (priv->otg_mode == USB_DR_MODE_PERIPHERAL) |
| priv->otg_phy_mode = USB_DR_MODE_PERIPHERAL; |
| else |
| priv->otg_phy_mode = USB_DR_MODE_HOST; |
| |
| for (i = 0 ; i < USB3_HOST_PHY ; ++i) { |
| if (!priv->phys[i].dev) |
| continue; |
| |
| regmap_update_bits(priv->regmap, U2P_R0 + (U2P_REG_SIZE * i), |
| U2P_R0_POWER_ON_RESET, |
| U2P_R0_POWER_ON_RESET); |
| |
| if (i == USB2_OTG_PHY) { |
| regmap_update_bits(priv->regmap, |
| U2P_R0 + (U2P_REG_SIZE * i), |
| U2P_R0_ID_PULLUP | U2P_R0_DRV_VBUS, |
| U2P_R0_ID_PULLUP | U2P_R0_DRV_VBUS); |
| |
| dwc3_meson_g12a_usb2_set_mode(priv, i, |
| priv->otg_phy_mode); |
| } else |
| dwc3_meson_g12a_usb2_set_mode(priv, i, |
| USB_DR_MODE_HOST); |
| |
| regmap_update_bits(priv->regmap, U2P_R0 + (U2P_REG_SIZE * i), |
| U2P_R0_POWER_ON_RESET, 0); |
| } |
| |
| return 0; |
| } |
| |
| static void dwc3_meson_g12a_usb3_init(struct dwc3_meson_g12a *priv) |
| { |
| regmap_update_bits(priv->regmap, USB_R3, |
| USB_R3_P30_SSC_RANGE_MASK | |
| USB_R3_P30_REF_SSP_EN, |
| USB_R3_P30_SSC_ENABLE | |
| FIELD_PREP(USB_R3_P30_SSC_RANGE_MASK, 2) | |
| USB_R3_P30_REF_SSP_EN); |
| udelay(2); |
| |
| regmap_update_bits(priv->regmap, USB_R2, |
| USB_R2_P30_PCS_TX_DEEMPH_3P5DB_MASK, |
| FIELD_PREP(USB_R2_P30_PCS_TX_DEEMPH_3P5DB_MASK, 0x15)); |
| |
| regmap_update_bits(priv->regmap, USB_R2, |
| USB_R2_P30_PCS_TX_DEEMPH_6DB_MASK, |
| FIELD_PREP(USB_R2_P30_PCS_TX_DEEMPH_6DB_MASK, 0x20)); |
| |
| udelay(2); |
| |
| regmap_update_bits(priv->regmap, USB_R1, |
| USB_R1_U3H_HOST_PORT_POWER_CONTROL_PRESENT, |
| USB_R1_U3H_HOST_PORT_POWER_CONTROL_PRESENT); |
| |
| regmap_update_bits(priv->regmap, USB_R1, |
| USB_R1_P30_PCS_TX_SWING_FULL_MASK, |
| FIELD_PREP(USB_R1_P30_PCS_TX_SWING_FULL_MASK, 127)); |
| } |
| |
| static void dwc3_meson_g12a_usb_init_mode(struct dwc3_meson_g12a *priv) |
| { |
| if (priv->otg_phy_mode == USB_DR_MODE_PERIPHERAL) { |
| regmap_update_bits(priv->regmap, USB_R0, |
| USB_R0_U2D_ACT, USB_R0_U2D_ACT); |
| regmap_update_bits(priv->regmap, USB_R0, |
| USB_R0_U2D_SS_SCALEDOWN_MODE_MASK, 0); |
| regmap_update_bits(priv->regmap, USB_R4, |
| USB_R4_P21_SLEEP_M0, USB_R4_P21_SLEEP_M0); |
| } else { |
| 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 int dwc3_meson_g12a_usb_init(struct dwc3_meson_g12a *priv) |
| { |
| int ret; |
| |
| ret = dwc3_meson_g12a_usb2_init(priv); |
| if (ret) |
| return ret; |
| |
| 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_R5, |
| USB_R5_ID_DIG_EN_0, |
| USB_R5_ID_DIG_EN_0); |
| regmap_update_bits(priv->regmap, USB_R5, |
| USB_R5_ID_DIG_EN_1, |
| USB_R5_ID_DIG_EN_1); |
| regmap_update_bits(priv->regmap, USB_R5, |
| USB_R5_ID_DIG_TH_MASK, |
| FIELD_PREP(USB_R5_ID_DIG_TH_MASK, 0xff)); |
| |
| /* If we have an actual SuperSpeed port, initialize it */ |
| if (priv->usb3_ports) |
| dwc3_meson_g12a_usb3_init(priv); |
| |
| dwc3_meson_g12a_usb_init_mode(priv); |
| |
| return 0; |
| } |
| |
| static int dwc3_meson_g12a_force_mode(struct udevice *dev, enum usb_dr_mode mode) |
| { |
| struct dwc3_meson_g12a *priv = dev_get_plat(dev); |
| |
| if (!priv) |
| return -EINVAL; |
| |
| if (mode != USB_DR_MODE_HOST && mode != USB_DR_MODE_PERIPHERAL) |
| return -EINVAL; |
| |
| if (!priv->phys[USB2_OTG_PHY].dev) |
| return -EINVAL; |
| |
| if (mode == USB_DR_MODE_HOST) |
| debug("%s: switching to Host Mode\n", __func__); |
| else |
| debug("%s: switching to Device Mode\n", __func__); |
| |
| #if CONFIG_IS_ENABLED(DM_REGULATOR) |
| if (priv->vbus_supply) { |
| int ret = regulator_set_enable(priv->vbus_supply, |
| (mode == USB_DR_MODE_PERIPHERAL)); |
| if (ret) |
| return ret; |
| } |
| #endif |
| priv->otg_phy_mode = mode; |
| |
| dwc3_meson_g12a_usb2_set_mode(priv, USB2_OTG_PHY, mode); |
| |
| dwc3_meson_g12a_usb_init_mode(priv); |
| |
| return 0; |
| } |
| |
| static int dwc3_meson_g12a_get_phys(struct dwc3_meson_g12a *priv) |
| { |
| struct dwc3_meson_g12a_drvdata *data = priv->drvdata; |
| int i, ret; |
| |
| for (i = 0 ; i < data->phy_cnt; ++i) { |
| ret = generic_phy_get_by_name(priv->dev, data->phy_names[i], |
| &priv->phys[i]); |
| if (ret == -ENOENT || ret == -ENODATA) |
| continue; |
| |
| if (ret) |
| return ret; |
| |
| if (i == USB3_HOST_PHY) |
| priv->usb3_ports++; |
| else |
| priv->usb2_ports++; |
| } |
| |
| debug("%s: usb2 ports: %d\n", __func__, priv->usb2_ports); |
| debug("%s: usb3 ports: %d\n", __func__, priv->usb3_ports); |
| |
| return 0; |
| } |
| |
| static int dwc3_meson_g12a_reset_init(struct dwc3_meson_g12a *priv) |
| { |
| int ret; |
| |
| ret = reset_get_by_index(priv->dev, 0, &priv->reset); |
| if (ret) |
| return ret; |
| |
| ret = reset_assert(&priv->reset); |
| udelay(1); |
| ret |= reset_deassert(&priv->reset); |
| if (ret) { |
| reset_free(&priv->reset); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int dwc3_meson_g12a_clk_init(struct dwc3_meson_g12a *priv) |
| { |
| int ret; |
| |
| ret = clk_get_by_index(priv->dev, 0, &priv->clk); |
| if (ret) |
| return ret; |
| |
| #if CONFIG_IS_ENABLED(CLK) |
| ret = clk_enable(&priv->clk); |
| if (ret) |
| return ret; |
| #endif |
| |
| return 0; |
| } |
| |
| static int dwc3_meson_a1_clk_init(struct dwc3_meson_g12a *priv) |
| { |
| int ret; |
| |
| ret = clk_get_by_name(priv->dev, "usb_bus", &priv->clk); |
| if (ret) |
| return ret; |
| |
| ret = clk_enable(&priv->clk); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| static int dwc3_meson_g12a_probe(struct udevice *dev) |
| { |
| struct dwc3_meson_g12a *priv = dev_get_plat(dev); |
| struct dwc3_meson_g12a_drvdata *data = |
| (struct dwc3_meson_g12a_drvdata *)dev_get_driver_data(dev); |
| int ret, i; |
| |
| priv->drvdata = data; |
| priv->dev = dev; |
| |
| ret = regmap_init_mem(dev_ofnode(dev), &priv->regmap); |
| if (ret) |
| return ret; |
| |
| ret = data->clk_init(priv); |
| if (ret) |
| return ret; |
| |
| ret = dwc3_meson_g12a_reset_init(priv); |
| if (ret) |
| return ret; |
| |
| ret = dwc3_meson_g12a_get_phys(priv); |
| if (ret) |
| return ret; |
| |
| #if CONFIG_IS_ENABLED(DM_REGULATOR) |
| ret = device_get_supply_regulator(dev, "vbus-supply", |
| &priv->vbus_supply); |
| if (ret && ret != -ENOENT) { |
| pr_err("Failed to get PHY regulator\n"); |
| return ret; |
| } |
| |
| if (priv->vbus_supply) { |
| ret = regulator_set_enable(priv->vbus_supply, true); |
| if (ret) |
| return ret; |
| } |
| #endif |
| |
| priv->otg_mode = usb_get_dr_mode(dev_ofnode(dev)); |
| |
| ret = dwc3_meson_g12a_usb_init(priv); |
| if (ret) |
| return ret; |
| |
| for (i = 0 ; i < data->phy_cnt; ++i) { |
| if (!priv->phys[i].dev) |
| continue; |
| |
| ret = generic_phy_init(&priv->phys[i]); |
| if (ret) |
| goto err_phy_init; |
| } |
| |
| for (i = 0; i < data->phy_cnt; ++i) { |
| if (!priv->phys[i].dev) |
| continue; |
| |
| ret = generic_phy_power_on(&priv->phys[i]); |
| if (ret) |
| goto err_phy_init; |
| } |
| |
| return 0; |
| |
| err_phy_init: |
| for (i = 0 ; i < data->phy_cnt ; ++i) { |
| if (!priv->phys[i].dev) |
| continue; |
| |
| generic_phy_exit(&priv->phys[i]); |
| } |
| |
| return ret; |
| } |
| |
| static int dwc3_meson_g12a_remove(struct udevice *dev) |
| { |
| struct dwc3_meson_g12a *priv = dev_get_plat(dev); |
| struct dwc3_meson_g12a_drvdata *data = priv->drvdata; |
| int i; |
| |
| reset_release_all(&priv->reset, 1); |
| |
| clk_release_all(&priv->clk, 1); |
| |
| for (i = 0; i < data->phy_cnt; ++i) { |
| if (!priv->phys[i].dev) |
| continue; |
| |
| generic_phy_power_off(&priv->phys[i]); |
| } |
| |
| for (i = 0 ; i < data->phy_cnt; ++i) { |
| if (!priv->phys[i].dev) |
| continue; |
| |
| generic_phy_exit(&priv->phys[i]); |
| } |
| |
| return dm_scan_fdt_dev(dev); |
| } |
| |
| static const struct dwc3_meson_g12a_drvdata meson_g12a_drvdata = { |
| .phy_names = dwc3_meson_g12a_phy_names, |
| .phy_cnt = ARRAY_SIZE(dwc3_meson_g12a_phy_names), |
| .clk_init = dwc3_meson_g12a_clk_init, |
| }; |
| |
| static const struct dwc3_meson_g12a_drvdata meson_a1_drvdata = { |
| .phy_names = dwc3_meson_a1_phy_names, |
| .phy_cnt = ARRAY_SIZE(dwc3_meson_a1_phy_names), |
| .clk_init = dwc3_meson_a1_clk_init, |
| }; |
| |
| static int dwc3_meson_g12a_child_pre_probe(struct udevice *dev) |
| { |
| if (ofnode_device_is_compatible(dev_ofnode(dev), "amlogic,meson-g12a-usb")) |
| return dwc3_meson_g12a_force_mode(dev->parent, USB_DR_MODE_PERIPHERAL); |
| |
| if (ofnode_device_is_compatible(dev_ofnode(dev), "amlogic,meson-a1-usb")) |
| return dwc3_meson_g12a_force_mode(dev->parent, USB_DR_MODE_PERIPHERAL); |
| |
| return 0; |
| } |
| |
| static int dwc3_meson_g12a_child_post_remove(struct udevice *dev) |
| { |
| if (ofnode_device_is_compatible(dev_ofnode(dev), "amlogic,meson-g12a-usb")) |
| return dwc3_meson_g12a_force_mode(dev->parent, USB_DR_MODE_HOST); |
| |
| if (ofnode_device_is_compatible(dev_ofnode(dev), "amlogic,meson-a1-usb")) |
| return dwc3_meson_g12a_force_mode(dev->parent, USB_DR_MODE_HOST); |
| |
| return 0; |
| } |
| |
| static const struct udevice_id dwc3_meson_g12a_ids[] = { |
| { |
| .compatible = "amlogic,meson-g12a-usb-ctrl", |
| .data = (ulong)&meson_g12a_drvdata, |
| }, |
| { |
| .compatible = "amlogic,meson-a1-usb-ctrl", |
| .data = (ulong)&meson_a1_drvdata, |
| }, |
| { } |
| }; |
| |
| U_BOOT_DRIVER(dwc3_generic_wrapper) = { |
| .name = "dwc3-meson-g12a", |
| .id = UCLASS_SIMPLE_BUS, |
| .of_match = dwc3_meson_g12a_ids, |
| .probe = dwc3_meson_g12a_probe, |
| .remove = dwc3_meson_g12a_remove, |
| .child_pre_probe = dwc3_meson_g12a_child_pre_probe, |
| .child_post_remove = dwc3_meson_g12a_child_post_remove, |
| .plat_auto = sizeof(struct dwc3_meson_g12a), |
| |
| }; |