On 25/03/19 3:09 PM, Neil Armstrong wrote: > This adds support for the shared USB3 + PCIE PHY found in the > Amlogic G12A SoC Family. > > It supports USB3 Host mode or PCIE 2.0 mode, depending on the layout of > the board. > > Selection is done by the #phy-cells, making the mode static and exclusive. > > Signed-off-by: Neil Armstrong <narmstrong@xxxxxxxxxxxx> > Reviewed-by: Martin Blumenstingl <martin.blumenstingl@xxxxxxxxxxxxxx> merged 1,2, 5 and 6 patches of this series. Thanks Kishon > --- > drivers/phy/amlogic/Kconfig | 11 + > drivers/phy/amlogic/Makefile | 1 + > .../phy/amlogic/phy-meson-g12a-usb3-pcie.c | 413 ++++++++++++++++++ > 3 files changed, 425 insertions(+) > create mode 100644 drivers/phy/amlogic/phy-meson-g12a-usb3-pcie.c > > diff --git a/drivers/phy/amlogic/Kconfig b/drivers/phy/amlogic/Kconfig > index 560ff0f1ed4c..4c08c1ccdd04 100644 > --- a/drivers/phy/amlogic/Kconfig > +++ b/drivers/phy/amlogic/Kconfig > @@ -47,3 +47,14 @@ config PHY_MESON_G12A_USB2 > Enable this to support the Meson USB2 PHYs found in Meson > G12A SoCs. > If unsure, say N. > + > +config PHY_MESON_G12A_USB3_PCIE > + tristate "Meson G12A USB3+PCIE Combo PHY driver" > + default ARCH_MESON > + depends on OF && (ARCH_MESON || COMPILE_TEST) > + select GENERIC_PHY > + select REGMAP_MMIO > + help > + Enable this to support the Meson USB3 + PCIE Combo PHY found > + in Meson G12A SoCs. > + If unsure, say N. > diff --git a/drivers/phy/amlogic/Makefile b/drivers/phy/amlogic/Makefile > index 7d4d10f5a6b3..fdd008e1b19b 100644 > --- a/drivers/phy/amlogic/Makefile > +++ b/drivers/phy/amlogic/Makefile > @@ -2,3 +2,4 @@ obj-$(CONFIG_PHY_MESON8B_USB2) += phy-meson8b-usb2.o > obj-$(CONFIG_PHY_MESON_GXL_USB2) += phy-meson-gxl-usb2.o > obj-$(CONFIG_PHY_MESON_G12A_USB2) += phy-meson-g12a-usb2.o > obj-$(CONFIG_PHY_MESON_GXL_USB3) += phy-meson-gxl-usb3.o > +obj-$(CONFIG_PHY_MESON_G12A_USB3_PCIE) += phy-meson-g12a-usb3-pcie.o > diff --git a/drivers/phy/amlogic/phy-meson-g12a-usb3-pcie.c b/drivers/phy/amlogic/phy-meson-g12a-usb3-pcie.c > new file mode 100644 > index 000000000000..6233a7979a93 > --- /dev/null > +++ b/drivers/phy/amlogic/phy-meson-g12a-usb3-pcie.c > @@ -0,0 +1,413 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Amlogic G12A USB3 + PCIE Combo PHY driver > + * > + * Copyright (C) 2017 Amlogic, Inc. All rights reserved > + * Copyright (C) 2019 BayLibre, SAS > + * Author: Neil Armstrong <narmstrong@xxxxxxxxxxxx> > + */ > + > +#include <linux/bitfield.h> > +#include <linux/bitops.h> > +#include <linux/clk.h> > +#include <linux/module.h> > +#include <linux/of_device.h> > +#include <linux/phy/phy.h> > +#include <linux/regmap.h> > +#include <linux/reset.h> > +#include <linux/platform_device.h> > +#include <dt-bindings/phy/phy.h> > + > +#define PHY_R0 0x00 > + #define PHY_R0_PCIE_POWER_STATE GENMASK(4, 0) > + #define PHY_R0_PCIE_USB3_SWITCH GENMASK(6, 5) > + > +#define PHY_R1 0x04 > + #define PHY_R1_PHY_TX1_TERM_OFFSET GENMASK(4, 0) > + #define PHY_R1_PHY_TX0_TERM_OFFSET GENMASK(9, 5) > + #define PHY_R1_PHY_RX1_EQ GENMASK(12, 10) > + #define PHY_R1_PHY_RX0_EQ GENMASK(15, 13) > + #define PHY_R1_PHY_LOS_LEVEL GENMASK(20, 16) > + #define PHY_R1_PHY_LOS_BIAS GENMASK(23, 21) > + #define PHY_R1_PHY_REF_CLKDIV2 BIT(24) > + #define PHY_R1_PHY_MPLL_MULTIPLIER GENMASK(31, 25) > + > +#define PHY_R2 0x08 > + #define PHY_R2_PCS_TX_DEEMPH_GEN2_6DB GENMASK(5, 0) > + #define PHY_R2_PCS_TX_DEEMPH_GEN2_3P5DB GENMASK(11, 6) > + #define PHY_R2_PCS_TX_DEEMPH_GEN1 GENMASK(17, 12) > + #define PHY_R2_PHY_TX_VBOOST_LVL GENMASK(20, 18) > + > +#define PHY_R4 0x10 > + #define PHY_R4_PHY_CR_WRITE BIT(0) > + #define PHY_R4_PHY_CR_READ BIT(1) > + #define PHY_R4_PHY_CR_DATA_IN GENMASK(17, 2) > + #define PHY_R4_PHY_CR_CAP_DATA BIT(18) > + #define PHY_R4_PHY_CR_CAP_ADDR BIT(19) > + > +#define PHY_R5 0x14 > + #define PHY_R5_PHY_CR_DATA_OUT GENMASK(15, 0) > + #define PHY_R5_PHY_CR_ACK BIT(16) > + #define PHY_R5_PHY_BS_OUT BIT(17) > + > +struct phy_g12a_usb3_pcie_priv { > + struct regmap *regmap; > + struct regmap *regmap_cr; > + struct clk *clk_ref; > + struct reset_control *reset; > + struct phy *phy; > + unsigned int mode; > +}; > + > +static const struct regmap_config phy_g12a_usb3_pcie_regmap_conf = { > + .reg_bits = 8, > + .val_bits = 32, > + .reg_stride = 4, > + .max_register = PHY_R5, > +}; > + > +static int phy_g12a_usb3_pcie_cr_bus_addr(struct phy_g12a_usb3_pcie_priv *priv, > + unsigned int addr) > +{ > + unsigned int val, reg; > + int ret; > + > + reg = FIELD_PREP(PHY_R4_PHY_CR_DATA_IN, addr); > + > + regmap_write(priv->regmap, PHY_R4, reg); > + regmap_write(priv->regmap, PHY_R4, reg); > + > + regmap_write(priv->regmap, PHY_R4, reg | PHY_R4_PHY_CR_CAP_ADDR); > + > + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, > + (val & PHY_R5_PHY_CR_ACK), > + 5, 1000); > + if (ret) > + return ret; > + > + regmap_write(priv->regmap, PHY_R4, reg); > + > + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, > + !(val & PHY_R5_PHY_CR_ACK), > + 5, 1000); > + if (ret) > + return ret; > + > + return 0; > +} > + > +static int phy_g12a_usb3_pcie_cr_bus_read(void *context, unsigned int addr, > + unsigned int *data) > +{ > + struct phy_g12a_usb3_pcie_priv *priv = context; > + unsigned int val; > + int ret; > + > + ret = phy_g12a_usb3_pcie_cr_bus_addr(priv, addr); > + if (ret) > + return ret; > + > + regmap_write(priv->regmap, PHY_R4, 0); > + regmap_write(priv->regmap, PHY_R4, PHY_R4_PHY_CR_READ); > + > + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, > + (val & PHY_R5_PHY_CR_ACK), > + 5, 1000); > + if (ret) > + return ret; > + > + *data = FIELD_GET(PHY_R5_PHY_CR_DATA_OUT, val); > + > + regmap_write(priv->regmap, PHY_R4, 0); > + > + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, > + !(val & PHY_R5_PHY_CR_ACK), > + 5, 1000); > + if (ret) > + return ret; > + > + return 0; > +} > + > +static int phy_g12a_usb3_pcie_cr_bus_write(void *context, unsigned int addr, > + unsigned int data) > +{ > + struct phy_g12a_usb3_pcie_priv *priv = context; > + unsigned int val, reg; > + int ret; > + > + ret = phy_g12a_usb3_pcie_cr_bus_addr(priv, addr); > + if (ret) > + return ret; > + > + reg = FIELD_PREP(PHY_R4_PHY_CR_DATA_IN, data); > + > + regmap_write(priv->regmap, PHY_R4, reg); > + regmap_write(priv->regmap, PHY_R4, reg); > + > + regmap_write(priv->regmap, PHY_R4, reg | PHY_R4_PHY_CR_CAP_DATA); > + > + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, > + (val & PHY_R5_PHY_CR_ACK), > + 5, 1000); > + if (ret) > + return ret; > + > + regmap_write(priv->regmap, PHY_R4, reg); > + > + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, > + (val & PHY_R5_PHY_CR_ACK) == 0, > + 5, 1000); > + if (ret) > + return ret; > + > + regmap_write(priv->regmap, PHY_R4, reg); > + > + regmap_write(priv->regmap, PHY_R4, reg | PHY_R4_PHY_CR_WRITE); > + > + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, > + (val & PHY_R5_PHY_CR_ACK), > + 5, 1000); > + if (ret) > + return ret; > + > + regmap_write(priv->regmap, PHY_R4, reg); > + > + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, > + (val & PHY_R5_PHY_CR_ACK) == 0, > + 5, 1000); > + if (ret) > + return ret; > + > + return 0; > +} > + > +static const struct regmap_config phy_g12a_usb3_pcie_cr_regmap_conf = { > + .reg_bits = 16, > + .val_bits = 16, > + .reg_read = phy_g12a_usb3_pcie_cr_bus_read, > + .reg_write = phy_g12a_usb3_pcie_cr_bus_write, > + .max_register = 0xffff, > + .fast_io = true, > +}; > + > +static int phy_g12a_usb3_init(struct phy *phy) > +{ > + struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy); > + int data, ret; > + > + /* Switch PHY to USB3 */ > + /* TODO figure out how to handle when PCIe was set in the bootloader */ > + regmap_update_bits(priv->regmap, PHY_R0, > + PHY_R0_PCIE_USB3_SWITCH, > + PHY_R0_PCIE_USB3_SWITCH); > + > + /* > + * WORKAROUND: There is SSPHY suspend bug due to > + * which USB enumerates > + * in HS mode instead of SS mode. Workaround it by asserting > + * LANE0.TX_ALT_BLOCK.EN_ALT_BUS to enable TX to use alt bus > + * mode > + */ > + ret = regmap_update_bits(priv->regmap_cr, 0x102d, BIT(7), BIT(7)); > + if (ret) > + return ret; > + > + ret = regmap_update_bits(priv->regmap_cr, 0x1010, 0xff0, 20); > + if (ret) > + return ret; > + > + /* > + * Fix RX Equalization setting as follows > + * LANE0.RX_OVRD_IN_HI. RX_EQ_EN set to 0 > + * LANE0.RX_OVRD_IN_HI.RX_EQ_EN_OVRD set to 1 > + * LANE0.RX_OVRD_IN_HI.RX_EQ set to 3 > + * LANE0.RX_OVRD_IN_HI.RX_EQ_OVRD set to 1 > + */ > + ret = regmap_read(priv->regmap_cr, 0x1006, &data); > + if (ret) > + return ret; > + > + data &= ~BIT(6); > + data |= BIT(7); > + data &= ~(0x7 << 8); > + data |= (0x3 << 8); > + data |= (1 << 11); > + ret = regmap_write(priv->regmap_cr, 0x1006, data); > + if (ret) > + return ret; > + > + /* > + * Set EQ and TX launch amplitudes as follows > + * LANE0.TX_OVRD_DRV_LO.PREEMPH set to 22 > + * LANE0.TX_OVRD_DRV_LO.AMPLITUDE set to 127 > + * LANE0.TX_OVRD_DRV_LO.EN set to 1. > + */ > + ret = regmap_read(priv->regmap_cr, 0x1002, &data); > + if (ret) > + return ret; > + > + data &= ~0x3f80; > + data |= (0x16 << 7); > + data &= ~0x7f; > + data |= (0x7f | BIT(14)); > + ret = regmap_write(priv->regmap_cr, 0x1002, data); > + if (ret) > + return ret; > + > + /* MPLL_LOOP_CTL.PROP_CNTRL = 8 */ > + ret = regmap_update_bits(priv->regmap_cr, 0x30, 0xf << 4, 8 << 4); > + if (ret) > + return ret; > + > + regmap_update_bits(priv->regmap, PHY_R2, > + PHY_R2_PHY_TX_VBOOST_LVL, > + FIELD_PREP(PHY_R2_PHY_TX_VBOOST_LVL, 0x4)); > + > + regmap_update_bits(priv->regmap, PHY_R1, > + PHY_R1_PHY_LOS_BIAS | PHY_R1_PHY_LOS_LEVEL, > + FIELD_PREP(PHY_R1_PHY_LOS_BIAS, 4) | > + FIELD_PREP(PHY_R1_PHY_LOS_LEVEL, 9)); > + > + return 0; > +} > + > +static int phy_g12a_usb3_pcie_init(struct phy *phy) > +{ > + struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy); > + int ret; > + > + ret = reset_control_reset(priv->reset); > + if (ret) > + return ret; > + > + if (priv->mode == PHY_TYPE_USB3) > + return phy_g12a_usb3_init(phy); > + > + /* Power UP PCIE */ > + /* TODO figure out when the bootloader has set USB3 mode before */ > + regmap_update_bits(priv->regmap, PHY_R0, > + PHY_R0_PCIE_POWER_STATE, > + FIELD_PREP(PHY_R0_PCIE_POWER_STATE, 0x1c)); > + > + return 0; > +} > + > +static int phy_g12a_usb3_pcie_exit(struct phy *phy) > +{ > + struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy); > + > + return reset_control_reset(priv->reset); > +} > + > +static struct phy *phy_g12a_usb3_pcie_xlate(struct device *dev, > + struct of_phandle_args *args) > +{ > + struct phy_g12a_usb3_pcie_priv *priv = dev_get_drvdata(dev); > + unsigned int mode; > + > + if (args->args_count < 1) { > + dev_err(dev, "invalid number of arguments\n"); > + return ERR_PTR(-EINVAL); > + } > + > + mode = args->args[0]; > + > + if (mode != PHY_TYPE_USB3 && mode != PHY_TYPE_PCIE) { > + dev_err(dev, "invalid phy mode select argument\n"); > + return ERR_PTR(-EINVAL); > + } > + > + priv->mode = mode; > + > + return priv->phy; > +} > + > +static const struct phy_ops phy_g12a_usb3_pcie_ops = { > + .init = phy_g12a_usb3_pcie_init, > + .exit = phy_g12a_usb3_pcie_exit, > + .owner = THIS_MODULE, > +}; > + > +static int phy_g12a_usb3_pcie_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct device_node *np = dev->of_node; > + struct phy_g12a_usb3_pcie_priv *priv; > + struct resource *res; > + struct phy_provider *phy_provider; > + void __iomem *base; > + int ret; > + > + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + base = devm_ioremap_resource(dev, res); > + if (IS_ERR(base)) > + return PTR_ERR(base); > + > + priv->regmap = devm_regmap_init_mmio(dev, base, > + &phy_g12a_usb3_pcie_regmap_conf); > + if (IS_ERR(priv->regmap)) > + return PTR_ERR(priv->regmap); > + > + priv->regmap_cr = devm_regmap_init(dev, NULL, priv, > + &phy_g12a_usb3_pcie_cr_regmap_conf); > + if (IS_ERR(priv->regmap_cr)) > + return PTR_ERR(priv->regmap_cr); > + > + priv->clk_ref = devm_clk_get(dev, "ref_clk"); > + if (IS_ERR(priv->clk_ref)) > + return PTR_ERR(priv->clk_ref); > + > + ret = clk_prepare_enable(priv->clk_ref); > + if (ret) > + goto err_disable_clk_ref; > + > + priv->reset = devm_reset_control_array_get(dev, false, false); > + if (IS_ERR(priv->reset)) > + return PTR_ERR(priv->reset); > + > + priv->phy = devm_phy_create(dev, np, &phy_g12a_usb3_pcie_ops); > + if (IS_ERR(priv->phy)) { > + ret = PTR_ERR(priv->phy); > + if (ret != -EPROBE_DEFER) > + dev_err(dev, "failed to create PHY\n"); > + > + return ret; > + } > + > + phy_set_drvdata(priv->phy, priv); > + dev_set_drvdata(dev, priv); > + > + phy_provider = devm_of_phy_provider_register(dev, > + phy_g12a_usb3_pcie_xlate); > + > + return PTR_ERR_OR_ZERO(phy_provider); > + > +err_disable_clk_ref: > + clk_disable_unprepare(priv->clk_ref); > + > + return ret; > +} > + > +static const struct of_device_id phy_g12a_usb3_pcie_of_match[] = { > + { .compatible = "amlogic,g12a-usb3-pcie-phy", }, > + { }, > +}; > +MODULE_DEVICE_TABLE(of, phy_g12a_usb3_pcie_of_match); > + > +static struct platform_driver phy_g12a_usb3_pcie_driver = { > + .probe = phy_g12a_usb3_pcie_probe, > + .driver = { > + .name = "phy-g12a-usb3-pcie", > + .of_match_table = phy_g12a_usb3_pcie_of_match, > + }, > +}; > +module_platform_driver(phy_g12a_usb3_pcie_driver); > + > +MODULE_AUTHOR("Neil Armstrong <narmstrong@xxxxxxxxxxxx>"); > +MODULE_DESCRIPTION("Amlogic G12A USB3 + PCIE Combo PHY driver"); > +MODULE_LICENSE("GPL v2"); >