diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/phy/marvell | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/phy/marvell')
-rw-r--r-- | drivers/phy/marvell/Kconfig | 138 | ||||
-rw-r--r-- | drivers/phy/marvell/Makefile | 15 | ||||
-rw-r--r-- | drivers/phy/marvell/phy-armada375-usb2.c | 147 | ||||
-rw-r--r-- | drivers/phy/marvell/phy-armada38x-comphy.c | 270 | ||||
-rw-r--r-- | drivers/phy/marvell/phy-berlin-sata.c | 297 | ||||
-rw-r--r-- | drivers/phy/marvell/phy-berlin-usb.c | 209 | ||||
-rw-r--r-- | drivers/phy/marvell/phy-mmp3-hsic.c | 80 | ||||
-rw-r--r-- | drivers/phy/marvell/phy-mmp3-usb.c | 289 | ||||
-rw-r--r-- | drivers/phy/marvell/phy-mvebu-a3700-comphy.c | 1375 | ||||
-rw-r--r-- | drivers/phy/marvell/phy-mvebu-a3700-utmi.c | 270 | ||||
-rw-r--r-- | drivers/phy/marvell/phy-mvebu-cp110-comphy.c | 1112 | ||||
-rw-r--r-- | drivers/phy/marvell/phy-mvebu-cp110-utmi.c | 384 | ||||
-rw-r--r-- | drivers/phy/marvell/phy-mvebu-sata.c | 127 | ||||
-rw-r--r-- | drivers/phy/marvell/phy-pxa-28nm-hsic.c | 209 | ||||
-rw-r--r-- | drivers/phy/marvell/phy-pxa-28nm-usb2.c | 341 | ||||
-rw-r--r-- | drivers/phy/marvell/phy-pxa-usb.c | 343 |
16 files changed, 5606 insertions, 0 deletions
diff --git a/drivers/phy/marvell/Kconfig b/drivers/phy/marvell/Kconfig new file mode 100644 index 000000000..bdb87c976 --- /dev/null +++ b/drivers/phy/marvell/Kconfig @@ -0,0 +1,138 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Phy drivers for Marvell platforms +# +config ARMADA375_USBCLUSTER_PHY + bool "Armada 375 USB cluster PHY support" if COMPILE_TEST + default y if MACH_ARMADA_375 + depends on OF && HAS_IOMEM + select GENERIC_PHY + +config PHY_BERLIN_SATA + tristate "Marvell Berlin SATA PHY driver" + depends on ARCH_BERLIN || COMPILE_TEST + depends on OF && HAS_IOMEM + select GENERIC_PHY + help + Enable this to support the SATA PHY on Marvell Berlin SoCs. + +config PHY_BERLIN_USB + tristate "Marvell Berlin USB PHY Driver" + depends on ARCH_BERLIN || COMPILE_TEST + depends on OF && HAS_IOMEM && RESET_CONTROLLER + select GENERIC_PHY + help + Enable this to support the USB PHY on Marvell Berlin SoCs. + +config PHY_MVEBU_A3700_COMPHY + tristate "Marvell A3700 comphy driver" + depends on ARCH_MVEBU || COMPILE_TEST + depends on OF + depends on HAVE_ARM_SMCCC + default y + select GENERIC_PHY + help + This driver allows to control the comphy, a hardware block providing + shared serdes PHYs on Marvell Armada 3700. Its serdes lanes can be + used by various controllers: Ethernet, SATA, USB3, PCIe. + +config PHY_MVEBU_A3700_UTMI + tristate "Marvell A3700 UTMI driver" + depends on ARCH_MVEBU || COMPILE_TEST + depends on OF + default y + select GENERIC_PHY + help + Enable this to support Marvell A3700 UTMI PHY driver. + +config PHY_MVEBU_A38X_COMPHY + tristate "Marvell Armada 38x comphy driver" + depends on ARCH_MVEBU || COMPILE_TEST + depends on OF + select GENERIC_PHY + help + This driver allows to control the comphy, an hardware block providing + shared serdes PHYs on Marvell Armada 38x. Its serdes lanes can be + used by various controllers (Ethernet, sata, usb, PCIe...). + +config PHY_MVEBU_CP110_COMPHY + tristate "Marvell CP110 comphy driver" + depends on ARCH_MVEBU || COMPILE_TEST + depends on OF + depends on HAVE_ARM_SMCCC + select GENERIC_PHY + help + This driver allows to control the comphy, an hardware block providing + shared serdes PHYs on Marvell Armada 7k/8k (in the CP110). Its serdes + lanes can be used by various controllers (Ethernet, sata, usb, + PCIe...). + +config PHY_MVEBU_CP110_UTMI + tristate "Marvell CP110 UTMI driver" + depends on ARCH_MVEBU || COMPILE_TEST + depends on OF && USB_COMMON + select GENERIC_PHY + help + Enable this to support Marvell CP110 UTMI PHY driver. + +config PHY_MVEBU_SATA + def_bool y + depends on ARCH_DOVE || MACH_DOVE || MACH_KIRKWOOD + depends on OF + select GENERIC_PHY + +config PHY_PXA_28NM_HSIC + tristate "Marvell USB HSIC 28nm PHY Driver" + depends on HAS_IOMEM + select GENERIC_PHY + help + Enable this to support Marvell USB HSIC PHY driver for Marvell + SoC. This driver will do the PHY initialization and shutdown. + The PHY driver will be used by Marvell ehci driver. + + To compile this driver as a module, choose M here. + +config PHY_PXA_28NM_USB2 + tristate "Marvell USB 2.0 28nm PHY Driver" + depends on HAS_IOMEM + select GENERIC_PHY + help + Enable this to support Marvell USB 2.0 PHY driver for Marvell + SoC. This driver will do the PHY initialization and shutdown. + The PHY driver will be used by Marvell udc/ehci/otg driver. + + To compile this driver as a module, choose M here. + +config PHY_PXA_USB + tristate "Marvell PXA USB PHY Driver" + depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST + select GENERIC_PHY + help + Enable this to support Marvell PXA USB PHY driver for Marvell + SoC. This driver will do the PHY initialization and shutdown. + The PHY driver will be used by Marvell udc/ehci/otg driver. + + To compile this driver as a module, choose M here. + +config PHY_MMP3_USB + tristate "Marvell MMP3 USB PHY Driver" + depends on MACH_MMP3_DT || COMPILE_TEST + select GENERIC_PHY + help + Enable this to support Marvell MMP3 USB PHY driver for Marvell + SoC. This driver will do the PHY initialization and shutdown. + The PHY driver will be used by Marvell udc/ehci/otg driver. + + To compile this driver as a module, choose M here. + +config PHY_MMP3_HSIC + tristate "Marvell MMP3 USB HSIC PHY Driver" + depends on MACH_MMP3_DT || COMPILE_TEST + select GENERIC_PHY + help + Enable this to support Marvell MMP3 USB HSIC PHY driver for + Marvell MMP3 SoC. This driver will be used my the Marvell EHCI + driver to initialize the interface to internal USB HSIC + components on MMP3-based boards. + + To compile this driver as a module, choose M here. diff --git a/drivers/phy/marvell/Makefile b/drivers/phy/marvell/Makefile new file mode 100644 index 000000000..90862c4da --- /dev/null +++ b/drivers/phy/marvell/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_ARMADA375_USBCLUSTER_PHY) += phy-armada375-usb2.o +obj-$(CONFIG_PHY_BERLIN_SATA) += phy-berlin-sata.o +obj-$(CONFIG_PHY_BERLIN_USB) += phy-berlin-usb.o +obj-$(CONFIG_PHY_MMP3_USB) += phy-mmp3-usb.o +obj-$(CONFIG_PHY_MMP3_HSIC) += phy-mmp3-hsic.o +obj-$(CONFIG_PHY_MVEBU_A3700_COMPHY) += phy-mvebu-a3700-comphy.o +obj-$(CONFIG_PHY_MVEBU_A3700_UTMI) += phy-mvebu-a3700-utmi.o +obj-$(CONFIG_PHY_MVEBU_A38X_COMPHY) += phy-armada38x-comphy.o +obj-$(CONFIG_PHY_MVEBU_CP110_COMPHY) += phy-mvebu-cp110-comphy.o +obj-$(CONFIG_PHY_MVEBU_CP110_UTMI) += phy-mvebu-cp110-utmi.o +obj-$(CONFIG_PHY_MVEBU_SATA) += phy-mvebu-sata.o +obj-$(CONFIG_PHY_PXA_28NM_HSIC) += phy-pxa-28nm-hsic.o +obj-$(CONFIG_PHY_PXA_28NM_USB2) += phy-pxa-28nm-usb2.o +obj-$(CONFIG_PHY_PXA_USB) += phy-pxa-usb.o diff --git a/drivers/phy/marvell/phy-armada375-usb2.c b/drivers/phy/marvell/phy-armada375-usb2.c new file mode 100644 index 000000000..b141e3cd8 --- /dev/null +++ b/drivers/phy/marvell/phy-armada375-usb2.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * USB cluster support for Armada 375 platform. + * + * Copyright (C) 2014 Marvell + * + * Gregory CLEMENT <gregory.clement@free-electrons.com> + * + * Armada 375 comes with an USB2 host and device controller and an + * USB3 controller. The USB cluster control register allows to manage + * common features of both USB controllers. + */ + +#include <dt-bindings/phy/phy.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/of_address.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> + +#define USB2_PHY_CONFIG_DISABLE BIT(0) + +struct armada375_cluster_phy { + struct phy *phy; + void __iomem *reg; + bool use_usb3; + int phy_provided; +}; + +static int armada375_usb_phy_init(struct phy *phy) +{ + struct armada375_cluster_phy *cluster_phy; + u32 reg; + + cluster_phy = phy_get_drvdata(phy); + if (!cluster_phy) + return -ENODEV; + + reg = readl(cluster_phy->reg); + if (cluster_phy->use_usb3) + reg |= USB2_PHY_CONFIG_DISABLE; + else + reg &= ~USB2_PHY_CONFIG_DISABLE; + writel(reg, cluster_phy->reg); + + return 0; +} + +static const struct phy_ops armada375_usb_phy_ops = { + .init = armada375_usb_phy_init, + .owner = THIS_MODULE, +}; + +/* + * Only one controller can use this PHY. We shouldn't have the case + * when two controllers want to use this PHY. But if this case occurs + * then we provide a phy to the first one and return an error for the + * next one. This error has also to be an error returned by + * devm_phy_optional_get() so different from ENODEV for USB2. In the + * USB3 case it still optional and we use ENODEV. + */ +static struct phy *armada375_usb_phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct armada375_cluster_phy *cluster_phy = dev_get_drvdata(dev); + + if (!cluster_phy) + return ERR_PTR(-ENODEV); + + /* + * Either the phy had never been requested and then the first + * usb claiming it can get it, or it had already been + * requested in this case, we only allow to use it with the + * same configuration. + */ + if (WARN_ON((cluster_phy->phy_provided != PHY_NONE) && + (cluster_phy->phy_provided != args->args[0]))) { + dev_err(dev, "This PHY has already been provided!\n"); + dev_err(dev, "Check your device tree, only one controller can use it\n."); + if (args->args[0] == PHY_TYPE_USB2) + return ERR_PTR(-EBUSY); + else + return ERR_PTR(-ENODEV); + } + + if (args->args[0] == PHY_TYPE_USB2) + cluster_phy->use_usb3 = false; + else if (args->args[0] == PHY_TYPE_USB3) + cluster_phy->use_usb3 = true; + else { + dev_err(dev, "Invalid PHY mode\n"); + return ERR_PTR(-ENODEV); + } + + /* Store which phy mode is used for next test */ + cluster_phy->phy_provided = args->args[0]; + + return cluster_phy->phy; +} + +static int armada375_usb_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phy *phy; + struct phy_provider *phy_provider; + void __iomem *usb_cluster_base; + struct armada375_cluster_phy *cluster_phy; + + cluster_phy = devm_kzalloc(dev, sizeof(*cluster_phy), GFP_KERNEL); + if (!cluster_phy) + return -ENOMEM; + + usb_cluster_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(usb_cluster_base)) + return PTR_ERR(usb_cluster_base); + + phy = devm_phy_create(dev, NULL, &armada375_usb_phy_ops); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create PHY\n"); + return PTR_ERR(phy); + } + + cluster_phy->phy = phy; + cluster_phy->reg = usb_cluster_base; + + dev_set_drvdata(dev, cluster_phy); + phy_set_drvdata(phy, cluster_phy); + + phy_provider = devm_of_phy_provider_register(&pdev->dev, + armada375_usb_phy_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id of_usb_cluster_table[] = { + { .compatible = "marvell,armada-375-usb-cluster", }, + { /* end of list */ }, +}; + +static struct platform_driver armada375_usb_phy_driver = { + .probe = armada375_usb_phy_probe, + .driver = { + .of_match_table = of_usb_cluster_table, + .name = "armada-375-usb-cluster", + } +}; +builtin_platform_driver(armada375_usb_phy_driver); diff --git a/drivers/phy/marvell/phy-armada38x-comphy.c b/drivers/phy/marvell/phy-armada38x-comphy.c new file mode 100644 index 000000000..0fe408964 --- /dev/null +++ b/drivers/phy/marvell/phy-armada38x-comphy.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Russell King, Deep Blue Solutions Ltd. + * + * Partly derived from CP110 comphy driver by Antoine Tenart + * <antoine.tenart@bootlin.com> + */ +#include <linux/delay.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/phy/phy.h> +#include <linux/phy.h> +#include <linux/platform_device.h> + +#define MAX_A38X_COMPHY 6 +#define MAX_A38X_PORTS 3 + +#define COMPHY_CFG1 0x00 +#define COMPHY_CFG1_GEN_TX(x) ((x) << 26) +#define COMPHY_CFG1_GEN_TX_MSK COMPHY_CFG1_GEN_TX(15) +#define COMPHY_CFG1_GEN_RX(x) ((x) << 22) +#define COMPHY_CFG1_GEN_RX_MSK COMPHY_CFG1_GEN_RX(15) +#define GEN_SGMII_1_25GBPS 6 +#define GEN_SGMII_3_125GBPS 8 + +#define COMPHY_STAT1 0x18 +#define COMPHY_STAT1_PLL_RDY_TX BIT(3) +#define COMPHY_STAT1_PLL_RDY_RX BIT(2) + +#define COMPHY_SELECTOR 0xfc + +struct a38x_comphy; + +struct a38x_comphy_lane { + void __iomem *base; + struct a38x_comphy *priv; + unsigned int n; + + int port; +}; + +struct a38x_comphy { + void __iomem *base; + void __iomem *conf; + struct device *dev; + struct a38x_comphy_lane lane[MAX_A38X_COMPHY]; +}; + +static const u8 gbe_mux[MAX_A38X_COMPHY][MAX_A38X_PORTS] = { + { 0, 0, 0 }, + { 4, 5, 0 }, + { 0, 4, 0 }, + { 0, 0, 4 }, + { 0, 3, 0 }, + { 0, 0, 3 }, +}; + +static void a38x_set_conf(struct a38x_comphy_lane *lane, bool enable) +{ + struct a38x_comphy *priv = lane->priv; + u32 conf; + + if (priv->conf) { + conf = readl_relaxed(priv->conf); + if (enable) + conf |= BIT(lane->port); + else + conf &= ~BIT(lane->port); + writel(conf, priv->conf); + } +} + +static void a38x_comphy_set_reg(struct a38x_comphy_lane *lane, + unsigned int offset, u32 mask, u32 value) +{ + u32 val; + + val = readl_relaxed(lane->base + offset) & ~mask; + writel(val | value, lane->base + offset); +} + +static void a38x_comphy_set_speed(struct a38x_comphy_lane *lane, + unsigned int gen_tx, unsigned int gen_rx) +{ + a38x_comphy_set_reg(lane, COMPHY_CFG1, + COMPHY_CFG1_GEN_TX_MSK | COMPHY_CFG1_GEN_RX_MSK, + COMPHY_CFG1_GEN_TX(gen_tx) | + COMPHY_CFG1_GEN_RX(gen_rx)); +} + +static int a38x_comphy_poll(struct a38x_comphy_lane *lane, + unsigned int offset, u32 mask, u32 value) +{ + u32 val; + int ret; + + ret = readl_relaxed_poll_timeout_atomic(lane->base + offset, val, + (val & mask) == value, + 1000, 150000); + + if (ret) + dev_err(lane->priv->dev, + "comphy%u: timed out waiting for status\n", lane->n); + + return ret; +} + +/* + * We only support changing the speed for comphys configured for GBE. + * Since that is all we do, we only poll for PLL ready status. + */ +static int a38x_comphy_set_mode(struct phy *phy, enum phy_mode mode, int sub) +{ + struct a38x_comphy_lane *lane = phy_get_drvdata(phy); + unsigned int gen; + int ret; + + if (mode != PHY_MODE_ETHERNET) + return -EINVAL; + + switch (sub) { + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_1000BASEX: + gen = GEN_SGMII_1_25GBPS; + break; + + case PHY_INTERFACE_MODE_2500BASEX: + gen = GEN_SGMII_3_125GBPS; + break; + + default: + return -EINVAL; + } + + a38x_set_conf(lane, false); + + a38x_comphy_set_speed(lane, gen, gen); + + ret = a38x_comphy_poll(lane, COMPHY_STAT1, + COMPHY_STAT1_PLL_RDY_TX | + COMPHY_STAT1_PLL_RDY_RX, + COMPHY_STAT1_PLL_RDY_TX | + COMPHY_STAT1_PLL_RDY_RX); + + if (ret == 0) + a38x_set_conf(lane, true); + + return ret; +} + +static const struct phy_ops a38x_comphy_ops = { + .set_mode = a38x_comphy_set_mode, + .owner = THIS_MODULE, +}; + +static struct phy *a38x_comphy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct a38x_comphy_lane *lane; + struct phy *phy; + u32 val; + + if (WARN_ON(args->args[0] >= MAX_A38X_PORTS)) + return ERR_PTR(-EINVAL); + + phy = of_phy_simple_xlate(dev, args); + if (IS_ERR(phy)) + return phy; + + lane = phy_get_drvdata(phy); + if (lane->port >= 0) + return ERR_PTR(-EBUSY); + + lane->port = args->args[0]; + + val = readl_relaxed(lane->priv->base + COMPHY_SELECTOR); + val = (val >> (4 * lane->n)) & 0xf; + + if (!gbe_mux[lane->n][lane->port] || + val != gbe_mux[lane->n][lane->port]) { + dev_warn(lane->priv->dev, + "comphy%u: not configured for GBE\n", lane->n); + phy = ERR_PTR(-EINVAL); + } + + return phy; +} + +static int a38x_comphy_probe(struct platform_device *pdev) +{ + struct phy_provider *provider; + struct device_node *child; + struct a38x_comphy *priv; + struct resource *res; + void __iomem *base; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->dev = &pdev->dev; + priv->base = base; + + /* Optional */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "conf"); + if (res) { + priv->conf = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->conf)) + return PTR_ERR(priv->conf); + } + + for_each_available_child_of_node(pdev->dev.of_node, child) { + struct phy *phy; + int ret; + u32 val; + + ret = of_property_read_u32(child, "reg", &val); + if (ret < 0) { + dev_err(&pdev->dev, "missing 'reg' property (%d)\n", + ret); + continue; + } + + if (val >= MAX_A38X_COMPHY || priv->lane[val].base) { + dev_err(&pdev->dev, "invalid 'reg' property\n"); + continue; + } + + phy = devm_phy_create(&pdev->dev, child, &a38x_comphy_ops); + if (IS_ERR(phy)) { + of_node_put(child); + return PTR_ERR(phy); + } + + priv->lane[val].base = base + 0x28 * val; + priv->lane[val].priv = priv; + priv->lane[val].n = val; + priv->lane[val].port = -1; + phy_set_drvdata(phy, &priv->lane[val]); + } + + dev_set_drvdata(&pdev->dev, priv); + + provider = devm_of_phy_provider_register(&pdev->dev, a38x_comphy_xlate); + + return PTR_ERR_OR_ZERO(provider); +} + +static const struct of_device_id a38x_comphy_of_match_table[] = { + { .compatible = "marvell,armada-380-comphy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, a38x_comphy_of_match_table); + +static struct platform_driver a38x_comphy_driver = { + .probe = a38x_comphy_probe, + .driver = { + .name = "armada-38x-comphy", + .of_match_table = a38x_comphy_of_match_table, + }, +}; +module_platform_driver(a38x_comphy_driver); + +MODULE_AUTHOR("Russell King <rmk+kernel@armlinux.org.uk>"); +MODULE_DESCRIPTION("Common PHY driver for Armada 38x SoCs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/marvell/phy-berlin-sata.c b/drivers/phy/marvell/phy-berlin-sata.c new file mode 100644 index 000000000..d70ba9bc4 --- /dev/null +++ b/drivers/phy/marvell/phy-berlin-sata.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Marvell Berlin SATA PHY driver + * + * Copyright (C) 2014 Marvell Technology Group Ltd. + * + * Antoine Ténart <antoine.tenart@free-electrons.com> + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/phy/phy.h> +#include <linux/io.h> +#include <linux/platform_device.h> + +#define HOST_VSA_ADDR 0x0 +#define HOST_VSA_DATA 0x4 +#define PORT_SCR_CTL 0x2c +#define PORT_VSR_ADDR 0x78 +#define PORT_VSR_DATA 0x7c + +#define CONTROL_REGISTER 0x0 +#define MBUS_SIZE_CONTROL 0x4 + +#define POWER_DOWN_PHY0 BIT(6) +#define POWER_DOWN_PHY1 BIT(14) +#define MBUS_WRITE_REQUEST_SIZE_128 (BIT(2) << 16) +#define MBUS_READ_REQUEST_SIZE_128 (BIT(2) << 19) + +#define BG2_PHY_BASE 0x080 +#define BG2Q_PHY_BASE 0x200 + +/* register 0x01 */ +#define REF_FREF_SEL_25 BIT(0) +#define PHY_BERLIN_MODE_SATA (0x0 << 5) + +/* register 0x02 */ +#define USE_MAX_PLL_RATE BIT(12) + +/* register 0x23 */ +#define DATA_BIT_WIDTH_10 (0x0 << 10) +#define DATA_BIT_WIDTH_20 (0x1 << 10) +#define DATA_BIT_WIDTH_40 (0x2 << 10) + +/* register 0x25 */ +#define PHY_GEN_MAX_1_5 (0x0 << 10) +#define PHY_GEN_MAX_3_0 (0x1 << 10) +#define PHY_GEN_MAX_6_0 (0x2 << 10) + +struct phy_berlin_desc { + struct phy *phy; + u32 power_bit; + unsigned index; +}; + +struct phy_berlin_priv { + void __iomem *base; + spinlock_t lock; + struct clk *clk; + struct phy_berlin_desc **phys; + unsigned nphys; + u32 phy_base; +}; + +static inline void phy_berlin_sata_reg_setbits(void __iomem *ctrl_reg, + u32 phy_base, u32 reg, u32 mask, u32 val) +{ + u32 regval; + + /* select register */ + writel(phy_base + reg, ctrl_reg + PORT_VSR_ADDR); + + /* set bits */ + regval = readl(ctrl_reg + PORT_VSR_DATA); + regval &= ~mask; + regval |= val; + writel(regval, ctrl_reg + PORT_VSR_DATA); +} + +static int phy_berlin_sata_power_on(struct phy *phy) +{ + struct phy_berlin_desc *desc = phy_get_drvdata(phy); + struct phy_berlin_priv *priv = dev_get_drvdata(phy->dev.parent); + void __iomem *ctrl_reg = priv->base + 0x60 + (desc->index * 0x80); + u32 regval; + + clk_prepare_enable(priv->clk); + + spin_lock(&priv->lock); + + /* Power on PHY */ + writel(CONTROL_REGISTER, priv->base + HOST_VSA_ADDR); + regval = readl(priv->base + HOST_VSA_DATA); + regval &= ~desc->power_bit; + writel(regval, priv->base + HOST_VSA_DATA); + + /* Configure MBus */ + writel(MBUS_SIZE_CONTROL, priv->base + HOST_VSA_ADDR); + regval = readl(priv->base + HOST_VSA_DATA); + regval |= MBUS_WRITE_REQUEST_SIZE_128 | MBUS_READ_REQUEST_SIZE_128; + writel(regval, priv->base + HOST_VSA_DATA); + + /* set PHY mode and ref freq to 25 MHz */ + phy_berlin_sata_reg_setbits(ctrl_reg, priv->phy_base, 0x01, + 0x00ff, + REF_FREF_SEL_25 | PHY_BERLIN_MODE_SATA); + + /* set PHY up to 6 Gbps */ + phy_berlin_sata_reg_setbits(ctrl_reg, priv->phy_base, 0x25, + 0x0c00, PHY_GEN_MAX_6_0); + + /* set 40 bits width */ + phy_berlin_sata_reg_setbits(ctrl_reg, priv->phy_base, 0x23, + 0x0c00, DATA_BIT_WIDTH_40); + + /* use max pll rate */ + phy_berlin_sata_reg_setbits(ctrl_reg, priv->phy_base, 0x02, + 0x0000, USE_MAX_PLL_RATE); + + /* set Gen3 controller speed */ + regval = readl(ctrl_reg + PORT_SCR_CTL); + regval &= ~GENMASK(7, 4); + regval |= 0x30; + writel(regval, ctrl_reg + PORT_SCR_CTL); + + spin_unlock(&priv->lock); + + clk_disable_unprepare(priv->clk); + + return 0; +} + +static int phy_berlin_sata_power_off(struct phy *phy) +{ + struct phy_berlin_desc *desc = phy_get_drvdata(phy); + struct phy_berlin_priv *priv = dev_get_drvdata(phy->dev.parent); + u32 regval; + + clk_prepare_enable(priv->clk); + + spin_lock(&priv->lock); + + /* Power down PHY */ + writel(CONTROL_REGISTER, priv->base + HOST_VSA_ADDR); + regval = readl(priv->base + HOST_VSA_DATA); + regval |= desc->power_bit; + writel(regval, priv->base + HOST_VSA_DATA); + + spin_unlock(&priv->lock); + + clk_disable_unprepare(priv->clk); + + return 0; +} + +static struct phy *phy_berlin_sata_phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct phy_berlin_priv *priv = dev_get_drvdata(dev); + int i; + + if (WARN_ON(args->args[0] >= priv->nphys)) + return ERR_PTR(-ENODEV); + + for (i = 0; i < priv->nphys; i++) { + if (priv->phys[i]->index == args->args[0]) + break; + } + + if (i == priv->nphys) + return ERR_PTR(-ENODEV); + + return priv->phys[i]->phy; +} + +static const struct phy_ops phy_berlin_sata_ops = { + .power_on = phy_berlin_sata_power_on, + .power_off = phy_berlin_sata_power_off, + .owner = THIS_MODULE, +}; + +static u32 phy_berlin_power_down_bits[] = { + POWER_DOWN_PHY0, + POWER_DOWN_PHY1, +}; + +static int phy_berlin_sata_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *child; + struct phy *phy; + struct phy_provider *phy_provider; + struct phy_berlin_priv *priv; + struct resource *res; + int ret, i = 0; + u32 phy_id; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + priv->base = devm_ioremap(dev, res->start, resource_size(res)); + if (!priv->base) + return -ENOMEM; + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + priv->nphys = of_get_child_count(dev->of_node); + if (priv->nphys == 0) + return -ENODEV; + + priv->phys = devm_kcalloc(dev, priv->nphys, sizeof(*priv->phys), + GFP_KERNEL); + if (!priv->phys) + return -ENOMEM; + + if (of_device_is_compatible(dev->of_node, "marvell,berlin2-sata-phy")) + priv->phy_base = BG2_PHY_BASE; + else + priv->phy_base = BG2Q_PHY_BASE; + + dev_set_drvdata(dev, priv); + spin_lock_init(&priv->lock); + + for_each_available_child_of_node(dev->of_node, child) { + struct phy_berlin_desc *phy_desc; + + if (of_property_read_u32(child, "reg", &phy_id)) { + dev_err(dev, "missing reg property in node %pOFn\n", + child); + ret = -EINVAL; + goto put_child; + } + + if (phy_id >= ARRAY_SIZE(phy_berlin_power_down_bits)) { + dev_err(dev, "invalid reg in node %pOFn\n", child); + ret = -EINVAL; + goto put_child; + } + + phy_desc = devm_kzalloc(dev, sizeof(*phy_desc), GFP_KERNEL); + if (!phy_desc) { + ret = -ENOMEM; + goto put_child; + } + + phy = devm_phy_create(dev, NULL, &phy_berlin_sata_ops); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create PHY %d\n", phy_id); + ret = PTR_ERR(phy); + goto put_child; + } + + phy_desc->phy = phy; + phy_desc->power_bit = phy_berlin_power_down_bits[phy_id]; + phy_desc->index = phy_id; + phy_set_drvdata(phy, phy_desc); + + priv->phys[i++] = phy_desc; + + /* Make sure the PHY is off */ + phy_berlin_sata_power_off(phy); + } + + phy_provider = + devm_of_phy_provider_register(dev, phy_berlin_sata_phy_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +put_child: + of_node_put(child); + return ret; +} + +static const struct of_device_id phy_berlin_sata_of_match[] = { + { .compatible = "marvell,berlin2-sata-phy" }, + { .compatible = "marvell,berlin2q-sata-phy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, phy_berlin_sata_of_match); + +static struct platform_driver phy_berlin_sata_driver = { + .probe = phy_berlin_sata_probe, + .driver = { + .name = "phy-berlin-sata", + .of_match_table = phy_berlin_sata_of_match, + }, +}; +module_platform_driver(phy_berlin_sata_driver); + +MODULE_DESCRIPTION("Marvell Berlin SATA PHY driver"); +MODULE_AUTHOR("Antoine Ténart <antoine.tenart@free-electrons.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/marvell/phy-berlin-usb.c b/drivers/phy/marvell/phy-berlin-usb.c new file mode 100644 index 000000000..78ef6ae72 --- /dev/null +++ b/drivers/phy/marvell/phy-berlin-usb.c @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2014 Marvell Technology Group Ltd. + * + * Antoine Tenart <antoine.tenart@free-electrons.com> + * Jisheng Zhang <jszhang@marvell.com> + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/reset.h> + +#define USB_PHY_PLL 0x04 +#define USB_PHY_PLL_CONTROL 0x08 +#define USB_PHY_TX_CTRL0 0x10 +#define USB_PHY_TX_CTRL1 0x14 +#define USB_PHY_TX_CTRL2 0x18 +#define USB_PHY_RX_CTRL 0x20 +#define USB_PHY_ANALOG 0x34 + +/* USB_PHY_PLL */ +#define CLK_REF_DIV(x) ((x) << 4) +#define FEEDBACK_CLK_DIV(x) ((x) << 8) + +/* USB_PHY_PLL_CONTROL */ +#define CLK_STABLE BIT(0) +#define PLL_CTRL_PIN BIT(1) +#define PLL_CTRL_REG BIT(2) +#define PLL_ON BIT(3) +#define PHASE_OFF_TOL_125 (0x0 << 5) +#define PHASE_OFF_TOL_250 BIT(5) +#define KVC0_CALIB (0x0 << 9) +#define KVC0_REG_CTRL BIT(9) +#define KVC0_HIGH (0x0 << 10) +#define KVC0_LOW (0x3 << 10) +#define CLK_BLK_EN BIT(13) + +/* USB_PHY_TX_CTRL0 */ +#define EXT_HS_RCAL_EN BIT(3) +#define EXT_FS_RCAL_EN BIT(4) +#define IMPCAL_VTH_DIV(x) ((x) << 5) +#define EXT_RS_RCAL_DIV(x) ((x) << 8) +#define EXT_FS_RCAL_DIV(x) ((x) << 12) + +/* USB_PHY_TX_CTRL1 */ +#define TX_VDD15_14 (0x0 << 4) +#define TX_VDD15_15 BIT(4) +#define TX_VDD15_16 (0x2 << 4) +#define TX_VDD15_17 (0x3 << 4) +#define TX_VDD12_VDD (0x0 << 6) +#define TX_VDD12_11 BIT(6) +#define TX_VDD12_12 (0x2 << 6) +#define TX_VDD12_13 (0x3 << 6) +#define LOW_VDD_EN BIT(8) +#define TX_OUT_AMP(x) ((x) << 9) + +/* USB_PHY_TX_CTRL2 */ +#define TX_CHAN_CTRL_REG(x) ((x) << 0) +#define DRV_SLEWRATE(x) ((x) << 4) +#define IMP_CAL_FS_HS_DLY_0 (0x0 << 6) +#define IMP_CAL_FS_HS_DLY_1 BIT(6) +#define IMP_CAL_FS_HS_DLY_2 (0x2 << 6) +#define IMP_CAL_FS_HS_DLY_3 (0x3 << 6) +#define FS_DRV_EN_MASK(x) ((x) << 8) +#define HS_DRV_EN_MASK(x) ((x) << 12) + +/* USB_PHY_RX_CTRL */ +#define PHASE_FREEZE_DLY_2_CL (0x0 << 0) +#define PHASE_FREEZE_DLY_4_CL BIT(0) +#define ACK_LENGTH_8_CL (0x0 << 2) +#define ACK_LENGTH_12_CL BIT(2) +#define ACK_LENGTH_16_CL (0x2 << 2) +#define ACK_LENGTH_20_CL (0x3 << 2) +#define SQ_LENGTH_3 (0x0 << 4) +#define SQ_LENGTH_6 BIT(4) +#define SQ_LENGTH_9 (0x2 << 4) +#define SQ_LENGTH_12 (0x3 << 4) +#define DISCON_THRESHOLD_260 (0x0 << 6) +#define DISCON_THRESHOLD_270 BIT(6) +#define DISCON_THRESHOLD_280 (0x2 << 6) +#define DISCON_THRESHOLD_290 (0x3 << 6) +#define SQ_THRESHOLD(x) ((x) << 8) +#define LPF_COEF(x) ((x) << 12) +#define INTPL_CUR_10 (0x0 << 14) +#define INTPL_CUR_20 BIT(14) +#define INTPL_CUR_30 (0x2 << 14) +#define INTPL_CUR_40 (0x3 << 14) + +/* USB_PHY_ANALOG */ +#define ANA_PWR_UP BIT(1) +#define ANA_PWR_DOWN BIT(2) +#define V2I_VCO_RATIO(x) ((x) << 7) +#define R_ROTATE_90 (0x0 << 10) +#define R_ROTATE_0 BIT(10) +#define MODE_TEST_EN BIT(11) +#define ANA_TEST_DC_CTRL(x) ((x) << 12) + +static const u32 phy_berlin_pll_dividers[] = { + /* Berlin 2 */ + CLK_REF_DIV(0x6) | FEEDBACK_CLK_DIV(0x55), + /* Berlin 2CD/Q */ + CLK_REF_DIV(0xc) | FEEDBACK_CLK_DIV(0x54), +}; + +struct phy_berlin_usb_priv { + void __iomem *base; + struct reset_control *rst_ctrl; + u32 pll_divider; +}; + +static int phy_berlin_usb_power_on(struct phy *phy) +{ + struct phy_berlin_usb_priv *priv = phy_get_drvdata(phy); + + reset_control_reset(priv->rst_ctrl); + + writel(priv->pll_divider, + priv->base + USB_PHY_PLL); + writel(CLK_STABLE | PLL_CTRL_REG | PHASE_OFF_TOL_250 | KVC0_REG_CTRL | + CLK_BLK_EN, priv->base + USB_PHY_PLL_CONTROL); + writel(V2I_VCO_RATIO(0x5) | R_ROTATE_0 | ANA_TEST_DC_CTRL(0x5), + priv->base + USB_PHY_ANALOG); + writel(PHASE_FREEZE_DLY_4_CL | ACK_LENGTH_16_CL | SQ_LENGTH_12 | + DISCON_THRESHOLD_270 | SQ_THRESHOLD(0xa) | LPF_COEF(0x2) | + INTPL_CUR_30, priv->base + USB_PHY_RX_CTRL); + + writel(TX_VDD12_13 | TX_OUT_AMP(0x3), priv->base + USB_PHY_TX_CTRL1); + writel(EXT_HS_RCAL_EN | IMPCAL_VTH_DIV(0x3) | EXT_RS_RCAL_DIV(0x4), + priv->base + USB_PHY_TX_CTRL0); + + writel(EXT_HS_RCAL_EN | IMPCAL_VTH_DIV(0x3) | EXT_RS_RCAL_DIV(0x4) | + EXT_FS_RCAL_DIV(0x2), priv->base + USB_PHY_TX_CTRL0); + + writel(EXT_HS_RCAL_EN | IMPCAL_VTH_DIV(0x3) | EXT_RS_RCAL_DIV(0x4), + priv->base + USB_PHY_TX_CTRL0); + writel(TX_CHAN_CTRL_REG(0xf) | DRV_SLEWRATE(0x3) | IMP_CAL_FS_HS_DLY_3 | + FS_DRV_EN_MASK(0xd), priv->base + USB_PHY_TX_CTRL2); + + return 0; +} + +static const struct phy_ops phy_berlin_usb_ops = { + .power_on = phy_berlin_usb_power_on, + .owner = THIS_MODULE, +}; + +static const struct of_device_id phy_berlin_usb_of_match[] = { + { + .compatible = "marvell,berlin2-usb-phy", + .data = &phy_berlin_pll_dividers[0], + }, + { + .compatible = "marvell,berlin2cd-usb-phy", + .data = &phy_berlin_pll_dividers[1], + }, + { }, +}; +MODULE_DEVICE_TABLE(of, phy_berlin_usb_of_match); + +static int phy_berlin_usb_probe(struct platform_device *pdev) +{ + const struct of_device_id *match = + of_match_device(phy_berlin_usb_of_match, &pdev->dev); + struct phy_berlin_usb_priv *priv; + struct phy *phy; + struct phy_provider *phy_provider; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->rst_ctrl = devm_reset_control_get(&pdev->dev, NULL); + if (IS_ERR(priv->rst_ctrl)) + return PTR_ERR(priv->rst_ctrl); + + priv->pll_divider = *((u32 *)match->data); + + phy = devm_phy_create(&pdev->dev, NULL, &phy_berlin_usb_ops); + if (IS_ERR(phy)) { + dev_err(&pdev->dev, "failed to create PHY\n"); + return PTR_ERR(phy); + } + + phy_set_drvdata(phy, priv); + + phy_provider = + devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static struct platform_driver phy_berlin_usb_driver = { + .probe = phy_berlin_usb_probe, + .driver = { + .name = "phy-berlin-usb", + .of_match_table = phy_berlin_usb_of_match, + }, +}; +module_platform_driver(phy_berlin_usb_driver); + +MODULE_AUTHOR("Antoine Tenart <antoine.tenart@free-electrons.com>"); +MODULE_DESCRIPTION("Marvell Berlin PHY driver for USB"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/marvell/phy-mmp3-hsic.c b/drivers/phy/marvell/phy-mmp3-hsic.c new file mode 100644 index 000000000..7cccf0184 --- /dev/null +++ b/drivers/phy/marvell/phy-mmp3-hsic.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2020 Lubomir Rintel <lkundrak@v3.sk> + */ + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> + +#define HSIC_CTRL 0x08 +#define HSIC_ENABLE BIT(7) +#define PLL_BYPASS BIT(4) + +static int mmp3_hsic_phy_init(struct phy *phy) +{ + void __iomem *base = (void __iomem *)phy_get_drvdata(phy); + u32 hsic_ctrl; + + hsic_ctrl = readl_relaxed(base + HSIC_CTRL); + hsic_ctrl |= HSIC_ENABLE; + hsic_ctrl |= PLL_BYPASS; + writel_relaxed(hsic_ctrl, base + HSIC_CTRL); + + return 0; +} + +static const struct phy_ops mmp3_hsic_phy_ops = { + .init = mmp3_hsic_phy_init, + .owner = THIS_MODULE, +}; + +static const struct of_device_id mmp3_hsic_phy_of_match[] = { + { .compatible = "marvell,mmp3-hsic-phy", }, + { }, +}; +MODULE_DEVICE_TABLE(of, mmp3_hsic_phy_of_match); + +static int mmp3_hsic_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phy_provider *provider; + struct resource *resource; + void __iomem *base; + struct phy *phy; + + resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, resource); + if (IS_ERR(base)) + return PTR_ERR(base); + + phy = devm_phy_create(dev, NULL, &mmp3_hsic_phy_ops); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create PHY\n"); + return PTR_ERR(phy); + } + + phy_set_drvdata(phy, (void *)base); + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(provider)) { + dev_err(dev, "failed to register PHY provider\n"); + return PTR_ERR(provider); + } + + return 0; +} + +static struct platform_driver mmp3_hsic_phy_driver = { + .probe = mmp3_hsic_phy_probe, + .driver = { + .name = "mmp3-hsic-phy", + .of_match_table = mmp3_hsic_phy_of_match, + }, +}; +module_platform_driver(mmp3_hsic_phy_driver); + +MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); +MODULE_DESCRIPTION("Marvell MMP3 USB HSIC PHY Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/marvell/phy-mmp3-usb.c b/drivers/phy/marvell/phy-mmp3-usb.c new file mode 100644 index 000000000..04c0bada3 --- /dev/null +++ b/drivers/phy/marvell/phy-mmp3-usb.c @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2011 Marvell International Ltd. All rights reserved. + * Copyright (C) 2018,2019 Lubomir Rintel <lkundrak@v3.sk> + */ + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/soc/mmp/cputype.h> + +#define USB2_PLL_REG0 0x4 +#define USB2_PLL_REG1 0x8 +#define USB2_TX_REG0 0x10 +#define USB2_TX_REG1 0x14 +#define USB2_TX_REG2 0x18 +#define USB2_RX_REG0 0x20 +#define USB2_RX_REG1 0x24 +#define USB2_RX_REG2 0x28 +#define USB2_ANA_REG0 0x30 +#define USB2_ANA_REG1 0x34 +#define USB2_ANA_REG2 0x38 +#define USB2_DIG_REG0 0x3C +#define USB2_DIG_REG1 0x40 +#define USB2_DIG_REG2 0x44 +#define USB2_DIG_REG3 0x48 +#define USB2_TEST_REG0 0x4C +#define USB2_TEST_REG1 0x50 +#define USB2_TEST_REG2 0x54 +#define USB2_CHARGER_REG0 0x58 +#define USB2_OTG_REG0 0x5C +#define USB2_PHY_MON0 0x60 +#define USB2_RESETVE_REG0 0x64 +#define USB2_ICID_REG0 0x78 +#define USB2_ICID_REG1 0x7C + +/* USB2_PLL_REG0 */ + +/* This is for Ax stepping */ +#define USB2_PLL_FBDIV_SHIFT_MMP3 0 +#define USB2_PLL_FBDIV_MASK_MMP3 (0xFF << 0) + +#define USB2_PLL_REFDIV_SHIFT_MMP3 8 +#define USB2_PLL_REFDIV_MASK_MMP3 (0xF << 8) + +#define USB2_PLL_VDD12_SHIFT_MMP3 12 +#define USB2_PLL_VDD18_SHIFT_MMP3 14 + +/* This is for B0 stepping */ +#define USB2_PLL_FBDIV_SHIFT_MMP3_B0 0 +#define USB2_PLL_REFDIV_SHIFT_MMP3_B0 9 +#define USB2_PLL_VDD18_SHIFT_MMP3_B0 14 +#define USB2_PLL_FBDIV_MASK_MMP3_B0 0x01FF +#define USB2_PLL_REFDIV_MASK_MMP3_B0 0x3E00 + +#define USB2_PLL_CAL12_SHIFT_MMP3 0 +#define USB2_PLL_CALI12_MASK_MMP3 (0x3 << 0) + +#define USB2_PLL_VCOCAL_START_SHIFT_MMP3 2 + +#define USB2_PLL_KVCO_SHIFT_MMP3 4 +#define USB2_PLL_KVCO_MASK_MMP3 (0x7<<4) + +#define USB2_PLL_ICP_SHIFT_MMP3 8 +#define USB2_PLL_ICP_MASK_MMP3 (0x7<<8) + +#define USB2_PLL_LOCK_BYPASS_SHIFT_MMP3 12 + +#define USB2_PLL_PU_PLL_SHIFT_MMP3 13 +#define USB2_PLL_PU_PLL_MASK (0x1 << 13) + +#define USB2_PLL_READY_MASK_MMP3 (0x1 << 15) + +/* USB2_TX_REG0 */ +#define USB2_TX_IMPCAL_VTH_SHIFT_MMP3 8 +#define USB2_TX_IMPCAL_VTH_MASK_MMP3 (0x7 << 8) + +#define USB2_TX_RCAL_START_SHIFT_MMP3 13 + +/* USB2_TX_REG1 */ +#define USB2_TX_CK60_PHSEL_SHIFT_MMP3 0 +#define USB2_TX_CK60_PHSEL_MASK_MMP3 (0xf << 0) + +#define USB2_TX_AMP_SHIFT_MMP3 4 +#define USB2_TX_AMP_MASK_MMP3 (0x7 << 4) + +#define USB2_TX_VDD12_SHIFT_MMP3 8 +#define USB2_TX_VDD12_MASK_MMP3 (0x3 << 8) + +/* USB2_TX_REG2 */ +#define USB2_TX_DRV_SLEWRATE_SHIFT 10 + +/* USB2_RX_REG0 */ +#define USB2_RX_SQ_THRESH_SHIFT_MMP3 4 +#define USB2_RX_SQ_THRESH_MASK_MMP3 (0xf << 4) + +#define USB2_RX_SQ_LENGTH_SHIFT_MMP3 10 +#define USB2_RX_SQ_LENGTH_MASK_MMP3 (0x3 << 10) + +/* USB2_ANA_REG1*/ +#define USB2_ANA_PU_ANA_SHIFT_MMP3 14 + +/* USB2_OTG_REG0 */ +#define USB2_OTG_PU_OTG_SHIFT_MMP3 3 + +struct mmp3_usb_phy { + struct phy *phy; + void __iomem *base; +}; + +static unsigned int u2o_get(void __iomem *base, unsigned int offset) +{ + return readl_relaxed(base + offset); +} + +static void u2o_set(void __iomem *base, unsigned int offset, + unsigned int value) +{ + u32 reg; + + reg = readl_relaxed(base + offset); + reg |= value; + writel_relaxed(reg, base + offset); + readl_relaxed(base + offset); +} + +static void u2o_clear(void __iomem *base, unsigned int offset, + unsigned int value) +{ + u32 reg; + + reg = readl_relaxed(base + offset); + reg &= ~value; + writel_relaxed(reg, base + offset); + readl_relaxed(base + offset); +} + +static int mmp3_usb_phy_init(struct phy *phy) +{ + struct mmp3_usb_phy *mmp3_usb_phy = phy_get_drvdata(phy); + void __iomem *base = mmp3_usb_phy->base; + + if (cpu_is_mmp3_a0()) { + u2o_clear(base, USB2_PLL_REG0, (USB2_PLL_FBDIV_MASK_MMP3 + | USB2_PLL_REFDIV_MASK_MMP3)); + u2o_set(base, USB2_PLL_REG0, + 0xd << USB2_PLL_REFDIV_SHIFT_MMP3 + | 0xf0 << USB2_PLL_FBDIV_SHIFT_MMP3); + } else if (cpu_is_mmp3_b0()) { + u2o_clear(base, USB2_PLL_REG0, USB2_PLL_REFDIV_MASK_MMP3_B0 + | USB2_PLL_FBDIV_MASK_MMP3_B0); + u2o_set(base, USB2_PLL_REG0, + 0xd << USB2_PLL_REFDIV_SHIFT_MMP3_B0 + | 0xf0 << USB2_PLL_FBDIV_SHIFT_MMP3_B0); + } else { + dev_err(&phy->dev, "unsupported silicon revision\n"); + return -ENODEV; + } + + u2o_clear(base, USB2_PLL_REG1, USB2_PLL_PU_PLL_MASK + | USB2_PLL_ICP_MASK_MMP3 + | USB2_PLL_KVCO_MASK_MMP3 + | USB2_PLL_CALI12_MASK_MMP3); + u2o_set(base, USB2_PLL_REG1, 1 << USB2_PLL_PU_PLL_SHIFT_MMP3 + | 1 << USB2_PLL_LOCK_BYPASS_SHIFT_MMP3 + | 3 << USB2_PLL_ICP_SHIFT_MMP3 + | 3 << USB2_PLL_KVCO_SHIFT_MMP3 + | 3 << USB2_PLL_CAL12_SHIFT_MMP3); + + u2o_clear(base, USB2_TX_REG0, USB2_TX_IMPCAL_VTH_MASK_MMP3); + u2o_set(base, USB2_TX_REG0, 2 << USB2_TX_IMPCAL_VTH_SHIFT_MMP3); + + u2o_clear(base, USB2_TX_REG1, USB2_TX_VDD12_MASK_MMP3 + | USB2_TX_AMP_MASK_MMP3 + | USB2_TX_CK60_PHSEL_MASK_MMP3); + u2o_set(base, USB2_TX_REG1, 3 << USB2_TX_VDD12_SHIFT_MMP3 + | 4 << USB2_TX_AMP_SHIFT_MMP3 + | 4 << USB2_TX_CK60_PHSEL_SHIFT_MMP3); + + u2o_clear(base, USB2_TX_REG2, 3 << USB2_TX_DRV_SLEWRATE_SHIFT); + u2o_set(base, USB2_TX_REG2, 2 << USB2_TX_DRV_SLEWRATE_SHIFT); + + u2o_clear(base, USB2_RX_REG0, USB2_RX_SQ_THRESH_MASK_MMP3); + u2o_set(base, USB2_RX_REG0, 0xa << USB2_RX_SQ_THRESH_SHIFT_MMP3); + + u2o_set(base, USB2_ANA_REG1, 0x1 << USB2_ANA_PU_ANA_SHIFT_MMP3); + + u2o_set(base, USB2_OTG_REG0, 0x1 << USB2_OTG_PU_OTG_SHIFT_MMP3); + + return 0; +} + +static int mmp3_usb_phy_calibrate(struct phy *phy) +{ + struct mmp3_usb_phy *mmp3_usb_phy = phy_get_drvdata(phy); + void __iomem *base = mmp3_usb_phy->base; + int loops; + + /* + * PLL VCO and TX Impedance Calibration Timing: + * + * _____________________________________ + * PU __________| + * _____________________________ + * VCOCAL START _________| + * ___ + * REG_RCAL_START ________________| |________|_______ + * | 200us | 400us | 40| 400us | USB PHY READY + */ + + udelay(200); + u2o_set(base, USB2_PLL_REG1, 1 << USB2_PLL_VCOCAL_START_SHIFT_MMP3); + udelay(400); + u2o_set(base, USB2_TX_REG0, 1 << USB2_TX_RCAL_START_SHIFT_MMP3); + udelay(40); + u2o_clear(base, USB2_TX_REG0, 1 << USB2_TX_RCAL_START_SHIFT_MMP3); + udelay(400); + + loops = 0; + while ((u2o_get(base, USB2_PLL_REG1) & USB2_PLL_READY_MASK_MMP3) == 0) { + mdelay(1); + loops++; + if (loops > 100) { + dev_err(&phy->dev, "PLL_READY not set after 100mS.\n"); + return -ETIMEDOUT; + } + } + + return 0; +} + +static const struct phy_ops mmp3_usb_phy_ops = { + .init = mmp3_usb_phy_init, + .calibrate = mmp3_usb_phy_calibrate, + .owner = THIS_MODULE, +}; + +static const struct of_device_id mmp3_usb_phy_of_match[] = { + { .compatible = "marvell,mmp3-usb-phy", }, + { }, +}; +MODULE_DEVICE_TABLE(of, mmp3_usb_phy_of_match); + +static int mmp3_usb_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mmp3_usb_phy *mmp3_usb_phy; + struct phy_provider *provider; + + mmp3_usb_phy = devm_kzalloc(dev, sizeof(*mmp3_usb_phy), GFP_KERNEL); + if (!mmp3_usb_phy) + return -ENOMEM; + + mmp3_usb_phy->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mmp3_usb_phy->base)) { + dev_err(dev, "failed to remap PHY regs\n"); + return PTR_ERR(mmp3_usb_phy->base); + } + + mmp3_usb_phy->phy = devm_phy_create(dev, NULL, &mmp3_usb_phy_ops); + if (IS_ERR(mmp3_usb_phy->phy)) { + dev_err(dev, "failed to create PHY\n"); + return PTR_ERR(mmp3_usb_phy->phy); + } + + phy_set_drvdata(mmp3_usb_phy->phy, mmp3_usb_phy); + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(provider)) { + dev_err(dev, "failed to register PHY provider\n"); + return PTR_ERR(provider); + } + + return 0; +} + +static struct platform_driver mmp3_usb_phy_driver = { + .probe = mmp3_usb_phy_probe, + .driver = { + .name = "mmp3-usb-phy", + .of_match_table = mmp3_usb_phy_of_match, + }, +}; +module_platform_driver(mmp3_usb_phy_driver); + +MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); +MODULE_DESCRIPTION("Marvell MMP3 USB PHY Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/marvell/phy-mvebu-a3700-comphy.c b/drivers/phy/marvell/phy-mvebu-a3700-comphy.c new file mode 100644 index 000000000..d641b345a --- /dev/null +++ b/drivers/phy/marvell/phy-mvebu-a3700-comphy.c @@ -0,0 +1,1375 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Marvell + * + * Authors: + * Evan Wang <xswang@marvell.com> + * Miquèl Raynal <miquel.raynal@bootlin.com> + * Pali Rohár <pali@kernel.org> + * Marek Behún <kabel@kernel.org> + * + * Structure inspired from phy-mvebu-cp110-comphy.c written by Antoine Tenart. + * Comphy code from ARM Trusted Firmware ported by Pali Rohár <pali@kernel.org> + * and Marek Behún <kabel@kernel.org>. + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/phy.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> + +#define PLL_SET_DELAY_US 600 +#define COMPHY_PLL_SLEEP 1000 +#define COMPHY_PLL_TIMEOUT 150000 + +/* Comphy lane2 indirect access register offset */ +#define COMPHY_LANE2_INDIR_ADDR 0x0 +#define COMPHY_LANE2_INDIR_DATA 0x4 + +/* SATA and USB3 PHY offset compared to SATA PHY */ +#define COMPHY_LANE2_REGS_BASE 0x200 + +/* + * When accessing common PHY lane registers directly, we need to shift by 1, + * since the registers are 16-bit. + */ +#define COMPHY_LANE_REG_DIRECT(reg) (((reg) & 0x7FF) << 1) + +/* COMPHY registers */ +#define COMPHY_POWER_PLL_CTRL 0x01 +#define PU_IVREF_BIT BIT(15) +#define PU_PLL_BIT BIT(14) +#define PU_RX_BIT BIT(13) +#define PU_TX_BIT BIT(12) +#define PU_TX_INTP_BIT BIT(11) +#define PU_DFE_BIT BIT(10) +#define RESET_DTL_RX_BIT BIT(9) +#define PLL_LOCK_BIT BIT(8) +#define REF_FREF_SEL_MASK GENMASK(4, 0) +#define REF_FREF_SEL_SERDES_25MHZ FIELD_PREP(REF_FREF_SEL_MASK, 0x1) +#define REF_FREF_SEL_SERDES_40MHZ FIELD_PREP(REF_FREF_SEL_MASK, 0x3) +#define REF_FREF_SEL_SERDES_50MHZ FIELD_PREP(REF_FREF_SEL_MASK, 0x4) +#define REF_FREF_SEL_PCIE_USB3_25MHZ FIELD_PREP(REF_FREF_SEL_MASK, 0x2) +#define REF_FREF_SEL_PCIE_USB3_40MHZ FIELD_PREP(REF_FREF_SEL_MASK, 0x3) +#define COMPHY_MODE_MASK GENMASK(7, 5) +#define COMPHY_MODE_SATA FIELD_PREP(COMPHY_MODE_MASK, 0x0) +#define COMPHY_MODE_PCIE FIELD_PREP(COMPHY_MODE_MASK, 0x3) +#define COMPHY_MODE_SERDES FIELD_PREP(COMPHY_MODE_MASK, 0x4) +#define COMPHY_MODE_USB3 FIELD_PREP(COMPHY_MODE_MASK, 0x5) + +#define COMPHY_KVCO_CAL_CTRL 0x02 +#define USE_MAX_PLL_RATE_BIT BIT(12) +#define SPEED_PLL_MASK GENMASK(7, 2) +#define SPEED_PLL_VALUE_16 FIELD_PREP(SPEED_PLL_MASK, 0x10) + +#define COMPHY_DIG_LOOPBACK_EN 0x23 +#define SEL_DATA_WIDTH_MASK GENMASK(11, 10) +#define DATA_WIDTH_10BIT FIELD_PREP(SEL_DATA_WIDTH_MASK, 0x0) +#define DATA_WIDTH_20BIT FIELD_PREP(SEL_DATA_WIDTH_MASK, 0x1) +#define DATA_WIDTH_40BIT FIELD_PREP(SEL_DATA_WIDTH_MASK, 0x2) +#define PLL_READY_TX_BIT BIT(4) + +#define COMPHY_SYNC_PATTERN 0x24 +#define TXD_INVERT_BIT BIT(10) +#define RXD_INVERT_BIT BIT(11) + +#define COMPHY_SYNC_MASK_GEN 0x25 +#define PHY_GEN_MAX_MASK GENMASK(11, 10) +#define PHY_GEN_MAX_USB3_5G FIELD_PREP(PHY_GEN_MAX_MASK, 0x1) + +#define COMPHY_ISOLATION_CTRL 0x26 +#define PHY_ISOLATE_MODE BIT(15) + +#define COMPHY_GEN2_SET2 0x3e +#define GS2_TX_SSC_AMP_MASK GENMASK(15, 9) +#define GS2_TX_SSC_AMP_4128 FIELD_PREP(GS2_TX_SSC_AMP_MASK, 0x20) +#define GS2_VREG_RXTX_MAS_ISET_MASK GENMASK(8, 7) +#define GS2_VREG_RXTX_MAS_ISET_60U FIELD_PREP(GS2_VREG_RXTX_MAS_ISET_MASK,\ + 0x0) +#define GS2_VREG_RXTX_MAS_ISET_80U FIELD_PREP(GS2_VREG_RXTX_MAS_ISET_MASK,\ + 0x1) +#define GS2_VREG_RXTX_MAS_ISET_100U FIELD_PREP(GS2_VREG_RXTX_MAS_ISET_MASK,\ + 0x2) +#define GS2_VREG_RXTX_MAS_ISET_120U FIELD_PREP(GS2_VREG_RXTX_MAS_ISET_MASK,\ + 0x3) +#define GS2_RSVD_6_0_MASK GENMASK(6, 0) + +#define COMPHY_GEN3_SET2 0x3f + +#define COMPHY_IDLE_SYNC_EN 0x48 +#define IDLE_SYNC_EN BIT(12) + +#define COMPHY_MISC_CTRL0 0x4F +#define CLK100M_125M_EN BIT(4) +#define TXDCLK_2X_SEL BIT(6) +#define CLK500M_EN BIT(7) +#define PHY_REF_CLK_SEL BIT(10) + +#define COMPHY_SFT_RESET 0x52 +#define SFT_RST BIT(9) +#define SFT_RST_NO_REG BIT(10) + +#define COMPHY_MISC_CTRL1 0x73 +#define SEL_BITS_PCIE_FORCE BIT(15) + +#define COMPHY_GEN2_SET3 0x112 +#define GS3_FFE_CAP_SEL_MASK GENMASK(3, 0) +#define GS3_FFE_CAP_SEL_VALUE FIELD_PREP(GS3_FFE_CAP_SEL_MASK, 0xF) + +/* PIPE registers */ +#define COMPHY_PIPE_LANE_CFG0 0x180 +#define PRD_TXDEEMPH0_MASK BIT(0) +#define PRD_TXMARGIN_MASK GENMASK(3, 1) +#define PRD_TXSWING_MASK BIT(4) +#define CFG_TX_ALIGN_POS_MASK GENMASK(8, 5) + +#define COMPHY_PIPE_LANE_CFG1 0x181 +#define PRD_TXDEEMPH1_MASK BIT(15) +#define USE_MAX_PLL_RATE_EN BIT(9) +#define TX_DET_RX_MODE BIT(6) +#define GEN2_TX_DATA_DLY_MASK GENMASK(4, 3) +#define GEN2_TX_DATA_DLY_DEFT FIELD_PREP(GEN2_TX_DATA_DLY_MASK, 2) +#define TX_ELEC_IDLE_MODE_EN BIT(0) + +#define COMPHY_PIPE_LANE_STAT1 0x183 +#define TXDCLK_PCLK_EN BIT(0) + +#define COMPHY_PIPE_LANE_CFG4 0x188 +#define SPREAD_SPECTRUM_CLK_EN BIT(7) + +#define COMPHY_PIPE_RST_CLK_CTRL 0x1C1 +#define PIPE_SOFT_RESET BIT(0) +#define PIPE_REG_RESET BIT(1) +#define MODE_CORE_CLK_FREQ_SEL BIT(9) +#define MODE_PIPE_WIDTH_32 BIT(3) +#define MODE_REFDIV_MASK GENMASK(5, 4) +#define MODE_REFDIV_BY_4 FIELD_PREP(MODE_REFDIV_MASK, 0x2) + +#define COMPHY_PIPE_TEST_MODE_CTRL 0x1C2 +#define MODE_MARGIN_OVERRIDE BIT(2) + +#define COMPHY_PIPE_CLK_SRC_LO 0x1C3 +#define MODE_CLK_SRC BIT(0) +#define BUNDLE_PERIOD_SEL BIT(1) +#define BUNDLE_PERIOD_SCALE_MASK GENMASK(3, 2) +#define BUNDLE_SAMPLE_CTRL BIT(4) +#define PLL_READY_DLY_MASK GENMASK(7, 5) +#define CFG_SEL_20B BIT(15) + +#define COMPHY_PIPE_PWR_MGM_TIM1 0x1D0 +#define CFG_PM_OSCCLK_WAIT_MASK GENMASK(15, 12) +#define CFG_PM_RXDEN_WAIT_MASK GENMASK(11, 8) +#define CFG_PM_RXDEN_WAIT_1_UNIT FIELD_PREP(CFG_PM_RXDEN_WAIT_MASK, 0x1) +#define CFG_PM_RXDLOZ_WAIT_MASK GENMASK(7, 0) +#define CFG_PM_RXDLOZ_WAIT_7_UNIT FIELD_PREP(CFG_PM_RXDLOZ_WAIT_MASK, 0x7) +#define CFG_PM_RXDLOZ_WAIT_12_UNIT FIELD_PREP(CFG_PM_RXDLOZ_WAIT_MASK, 0xC) + +/* + * This register is not from PHY lane register space. It only exists in the + * indirect register space, before the actual PHY lane 2 registers. So the + * offset is absolute, not relative to COMPHY_LANE2_REGS_BASE. + * It is used only for SATA PHY initialization. + */ +#define COMPHY_RESERVED_REG 0x0E +#define PHYCTRL_FRM_PIN_BIT BIT(13) + +/* South Bridge PHY Configuration Registers */ +#define COMPHY_PHY_REG(lane, reg) (((1 - (lane)) * 0x28) + ((reg) & 0x3f)) + +/* + * lane0: USB3/GbE1 PHY Configuration 1 + * lane1: PCIe/GbE0 PHY Configuration 1 + * (used only by SGMII code) + */ +#define COMPHY_PHY_CFG1 0x0 +#define PIN_PU_IVREF_BIT BIT(1) +#define PIN_RESET_CORE_BIT BIT(11) +#define PIN_RESET_COMPHY_BIT BIT(12) +#define PIN_PU_PLL_BIT BIT(16) +#define PIN_PU_RX_BIT BIT(17) +#define PIN_PU_TX_BIT BIT(18) +#define PIN_TX_IDLE_BIT BIT(19) +#define GEN_RX_SEL_MASK GENMASK(25, 22) +#define GEN_RX_SEL_VALUE(val) FIELD_PREP(GEN_RX_SEL_MASK, (val)) +#define GEN_TX_SEL_MASK GENMASK(29, 26) +#define GEN_TX_SEL_VALUE(val) FIELD_PREP(GEN_TX_SEL_MASK, (val)) +#define SERDES_SPEED_1_25_G 0x6 +#define SERDES_SPEED_3_125_G 0x8 +#define PHY_RX_INIT_BIT BIT(30) + +/* + * lane0: USB3/GbE1 PHY Status 1 + * lane1: PCIe/GbE0 PHY Status 1 + * (used only by SGMII code) + */ +#define COMPHY_PHY_STAT1 0x18 +#define PHY_RX_INIT_DONE_BIT BIT(0) +#define PHY_PLL_READY_RX_BIT BIT(2) +#define PHY_PLL_READY_TX_BIT BIT(3) + +/* PHY Selector */ +#define COMPHY_SELECTOR_PHY_REG 0xFC +/* bit0: 0: Lane1 is GbE0; 1: Lane1 is PCIe */ +#define COMPHY_SELECTOR_PCIE_GBE0_SEL_BIT BIT(0) +/* bit4: 0: Lane0 is GbE1; 1: Lane0 is USB3 */ +#define COMPHY_SELECTOR_USB3_GBE1_SEL_BIT BIT(4) +/* bit8: 0: Lane0 is USB3 instead of GbE1, Lane2 is SATA; 1: Lane2 is USB3 */ +#define COMPHY_SELECTOR_USB3_PHY_SEL_BIT BIT(8) + +struct mvebu_a3700_comphy_conf { + unsigned int lane; + enum phy_mode mode; + int submode; +}; + +#define MVEBU_A3700_COMPHY_CONF(_lane, _mode, _smode) \ + { \ + .lane = _lane, \ + .mode = _mode, \ + .submode = _smode, \ + } + +#define MVEBU_A3700_COMPHY_CONF_GEN(_lane, _mode) \ + MVEBU_A3700_COMPHY_CONF(_lane, _mode, PHY_INTERFACE_MODE_NA) + +#define MVEBU_A3700_COMPHY_CONF_ETH(_lane, _smode) \ + MVEBU_A3700_COMPHY_CONF(_lane, PHY_MODE_ETHERNET, _smode) + +static const struct mvebu_a3700_comphy_conf mvebu_a3700_comphy_modes[] = { + /* lane 0 */ + MVEBU_A3700_COMPHY_CONF_GEN(0, PHY_MODE_USB_HOST_SS), + MVEBU_A3700_COMPHY_CONF_ETH(0, PHY_INTERFACE_MODE_SGMII), + MVEBU_A3700_COMPHY_CONF_ETH(0, PHY_INTERFACE_MODE_1000BASEX), + MVEBU_A3700_COMPHY_CONF_ETH(0, PHY_INTERFACE_MODE_2500BASEX), + /* lane 1 */ + MVEBU_A3700_COMPHY_CONF_GEN(1, PHY_MODE_PCIE), + MVEBU_A3700_COMPHY_CONF_ETH(1, PHY_INTERFACE_MODE_SGMII), + MVEBU_A3700_COMPHY_CONF_ETH(1, PHY_INTERFACE_MODE_1000BASEX), + MVEBU_A3700_COMPHY_CONF_ETH(1, PHY_INTERFACE_MODE_2500BASEX), + /* lane 2 */ + MVEBU_A3700_COMPHY_CONF_GEN(2, PHY_MODE_SATA), + MVEBU_A3700_COMPHY_CONF_GEN(2, PHY_MODE_USB_HOST_SS), +}; + +struct mvebu_a3700_comphy_priv { + void __iomem *comphy_regs; + void __iomem *lane0_phy_regs; /* USB3 and GbE1 */ + void __iomem *lane1_phy_regs; /* PCIe and GbE0 */ + void __iomem *lane2_phy_indirect; /* SATA and USB3 */ + spinlock_t lock; /* for PHY selector access */ + bool xtal_is_40m; +}; + +struct mvebu_a3700_comphy_lane { + struct mvebu_a3700_comphy_priv *priv; + struct device *dev; + unsigned int id; + enum phy_mode mode; + int submode; + bool invert_tx; + bool invert_rx; +}; + +struct gbe_phy_init_data_fix { + u16 addr; + u16 value; +}; + +/* Changes to 40M1G25 mode data required for running 40M3G125 init mode */ +static struct gbe_phy_init_data_fix gbe_phy_init_fix[] = { + { 0x005, 0x07CC }, { 0x015, 0x0000 }, { 0x01B, 0x0000 }, + { 0x01D, 0x0000 }, { 0x01E, 0x0000 }, { 0x01F, 0x0000 }, + { 0x020, 0x0000 }, { 0x021, 0x0030 }, { 0x026, 0x0888 }, + { 0x04D, 0x0152 }, { 0x04F, 0xA020 }, { 0x050, 0x07CC }, + { 0x053, 0xE9CA }, { 0x055, 0xBD97 }, { 0x071, 0x3015 }, + { 0x076, 0x03AA }, { 0x07C, 0x0FDF }, { 0x0C2, 0x3030 }, + { 0x0C3, 0x8000 }, { 0x0E2, 0x5550 }, { 0x0E3, 0x12A4 }, + { 0x0E4, 0x7D00 }, { 0x0E6, 0x0C83 }, { 0x101, 0xFCC0 }, + { 0x104, 0x0C10 } +}; + +/* 40M1G25 mode init data */ +static u16 gbe_phy_init[512] = { + /* 0 1 2 3 4 5 6 7 */ + /*-----------------------------------------------------------*/ + /* 8 9 A B C D E F */ + 0x3110, 0xFD83, 0x6430, 0x412F, 0x82C0, 0x06FA, 0x4500, 0x6D26, /* 00 */ + 0xAFC0, 0x8000, 0xC000, 0x0000, 0x2000, 0x49CC, 0x0BC9, 0x2A52, /* 08 */ + 0x0BD2, 0x0CDE, 0x13D2, 0x0CE8, 0x1149, 0x10E0, 0x0000, 0x0000, /* 10 */ + 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x4134, 0x0D2D, 0xFFFF, /* 18 */ + 0xFFE0, 0x4030, 0x1016, 0x0030, 0x0000, 0x0800, 0x0866, 0x0000, /* 20 */ + 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, /* 28 */ + 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /* 30 */ + 0x0000, 0x0000, 0x000F, 0x6A62, 0x1988, 0x3100, 0x3100, 0x3100, /* 38 */ + 0x3100, 0xA708, 0x2430, 0x0830, 0x1030, 0x4610, 0xFF00, 0xFF00, /* 40 */ + 0x0060, 0x1000, 0x0400, 0x0040, 0x00F0, 0x0155, 0x1100, 0xA02A, /* 48 */ + 0x06FA, 0x0080, 0xB008, 0xE3ED, 0x5002, 0xB592, 0x7A80, 0x0001, /* 50 */ + 0x020A, 0x8820, 0x6014, 0x8054, 0xACAA, 0xFC88, 0x2A02, 0x45CF, /* 58 */ + 0x000F, 0x1817, 0x2860, 0x064F, 0x0000, 0x0204, 0x1800, 0x6000, /* 60 */ + 0x810F, 0x4F23, 0x4000, 0x4498, 0x0850, 0x0000, 0x000E, 0x1002, /* 68 */ + 0x9D3A, 0x3009, 0xD066, 0x0491, 0x0001, 0x6AB0, 0x0399, 0x3780, /* 70 */ + 0x0040, 0x5AC0, 0x4A80, 0x0000, 0x01DF, 0x0000, 0x0007, 0x0000, /* 78 */ + 0x2D54, 0x00A1, 0x4000, 0x0100, 0xA20A, 0x0000, 0x0000, 0x0000, /* 80 */ + 0x0000, 0x0000, 0x0000, 0x7400, 0x0E81, 0x1000, 0x1242, 0x0210, /* 88 */ + 0x80DF, 0x0F1F, 0x2F3F, 0x4F5F, 0x6F7F, 0x0F1F, 0x2F3F, 0x4F5F, /* 90 */ + 0x6F7F, 0x4BAD, 0x0000, 0x0000, 0x0800, 0x0000, 0x2400, 0xB651, /* 98 */ + 0xC9E0, 0x4247, 0x0A24, 0x0000, 0xAF19, 0x1004, 0x0000, 0x0000, /* A0 */ + 0x0000, 0x0013, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /* A8 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /* B0 */ + 0x0000, 0x0000, 0x0000, 0x0060, 0x0000, 0x0000, 0x0000, 0x0000, /* B8 */ + 0x0000, 0x0000, 0x3010, 0xFA00, 0x0000, 0x0000, 0x0000, 0x0003, /* C0 */ + 0x1618, 0x8200, 0x8000, 0x0400, 0x050F, 0x0000, 0x0000, 0x0000, /* C8 */ + 0x4C93, 0x0000, 0x1000, 0x1120, 0x0010, 0x1242, 0x1242, 0x1E00, /* D0 */ + 0x0000, 0x0000, 0x0000, 0x00F8, 0x0000, 0x0041, 0x0800, 0x0000, /* D8 */ + 0x82A0, 0x572E, 0x2490, 0x14A9, 0x4E00, 0x0000, 0x0803, 0x0541, /* E0 */ + 0x0C15, 0x0000, 0x0000, 0x0400, 0x2626, 0x0000, 0x0000, 0x4200, /* E8 */ + 0x0000, 0xAA55, 0x1020, 0x0000, 0x0000, 0x5010, 0x0000, 0x0000, /* F0 */ + 0x0000, 0x0000, 0x5000, 0x0000, 0x0000, 0x0000, 0x02F2, 0x0000, /* F8 */ + 0x101F, 0xFDC0, 0x4000, 0x8010, 0x0110, 0x0006, 0x0000, 0x0000, /*100 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*108 */ + 0x04CF, 0x0000, 0x04CF, 0x0000, 0x04CF, 0x0000, 0x04C6, 0x0000, /*110 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*118 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*120 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*128 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*130 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*138 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*140 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*148 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*150 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*158 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*160 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*168 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*170 */ + 0x0000, 0x0000, 0x0000, 0x00F0, 0x08A2, 0x3112, 0x0A14, 0x0000, /*178 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*180 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*188 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*190 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*198 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*1A0 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*1A8 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*1B0 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*1B8 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*1C0 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*1C8 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*1D0 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*1D8 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*1E0 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*1E8 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /*1F0 */ + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 /*1F8 */ +}; + +static inline void comphy_reg_set(void __iomem *addr, u32 data, u32 mask) +{ + u32 val; + + val = readl(addr); + val = (val & ~mask) | (data & mask); + writel(val, addr); +} + +static inline void comphy_reg_set16(void __iomem *addr, u16 data, u16 mask) +{ + u16 val; + + val = readw(addr); + val = (val & ~mask) | (data & mask); + writew(val, addr); +} + +/* Used for accessing lane 2 registers (SATA/USB3 PHY) */ +static void comphy_set_indirect(struct mvebu_a3700_comphy_priv *priv, + u32 offset, u16 data, u16 mask) +{ + writel(offset, + priv->lane2_phy_indirect + COMPHY_LANE2_INDIR_ADDR); + comphy_reg_set(priv->lane2_phy_indirect + COMPHY_LANE2_INDIR_DATA, + data, mask); +} + +static void comphy_lane_reg_set(struct mvebu_a3700_comphy_lane *lane, + u16 reg, u16 data, u16 mask) +{ + if (lane->id == 2) { + /* lane 2 PHY registers are accessed indirectly */ + comphy_set_indirect(lane->priv, + reg + COMPHY_LANE2_REGS_BASE, + data, mask); + } else { + void __iomem *base = lane->id == 1 ? + lane->priv->lane1_phy_regs : + lane->priv->lane0_phy_regs; + + comphy_reg_set16(base + COMPHY_LANE_REG_DIRECT(reg), + data, mask); + } +} + +static int comphy_lane_reg_poll(struct mvebu_a3700_comphy_lane *lane, + u16 reg, u16 bits, + ulong sleep_us, ulong timeout_us) +{ + int ret; + + if (lane->id == 2) { + u32 data; + + /* lane 2 PHY registers are accessed indirectly */ + writel(reg + COMPHY_LANE2_REGS_BASE, + lane->priv->lane2_phy_indirect + + COMPHY_LANE2_INDIR_ADDR); + + ret = readl_poll_timeout(lane->priv->lane2_phy_indirect + + COMPHY_LANE2_INDIR_DATA, + data, (data & bits) == bits, + sleep_us, timeout_us); + } else { + void __iomem *base = lane->id == 1 ? + lane->priv->lane1_phy_regs : + lane->priv->lane0_phy_regs; + u16 data; + + ret = readw_poll_timeout(base + COMPHY_LANE_REG_DIRECT(reg), + data, (data & bits) == bits, + sleep_us, timeout_us); + } + + return ret; +} + +static void comphy_periph_reg_set(struct mvebu_a3700_comphy_lane *lane, + u8 reg, u32 data, u32 mask) +{ + comphy_reg_set(lane->priv->comphy_regs + COMPHY_PHY_REG(lane->id, reg), + data, mask); +} + +static int comphy_periph_reg_poll(struct mvebu_a3700_comphy_lane *lane, + u8 reg, u32 bits, + ulong sleep_us, ulong timeout_us) +{ + u32 data; + + return readl_poll_timeout(lane->priv->comphy_regs + + COMPHY_PHY_REG(lane->id, reg), + data, (data & bits) == bits, + sleep_us, timeout_us); +} + +/* PHY selector configures with corresponding modes */ +static int +mvebu_a3700_comphy_set_phy_selector(struct mvebu_a3700_comphy_lane *lane) +{ + u32 old, new, clr = 0, set = 0; + unsigned long flags; + + switch (lane->mode) { + case PHY_MODE_SATA: + /* SATA must be in Lane2 */ + if (lane->id == 2) + clr = COMPHY_SELECTOR_USB3_PHY_SEL_BIT; + else + goto error; + break; + + case PHY_MODE_ETHERNET: + if (lane->id == 0) + clr = COMPHY_SELECTOR_USB3_GBE1_SEL_BIT; + else if (lane->id == 1) + clr = COMPHY_SELECTOR_PCIE_GBE0_SEL_BIT; + else + goto error; + break; + + case PHY_MODE_USB_HOST_SS: + if (lane->id == 2) + set = COMPHY_SELECTOR_USB3_PHY_SEL_BIT; + else if (lane->id == 0) + set = COMPHY_SELECTOR_USB3_GBE1_SEL_BIT; + else + goto error; + break; + + case PHY_MODE_PCIE: + /* PCIE must be in Lane1 */ + if (lane->id == 1) + set = COMPHY_SELECTOR_PCIE_GBE0_SEL_BIT; + else + goto error; + break; + + default: + goto error; + } + + spin_lock_irqsave(&lane->priv->lock, flags); + + old = readl(lane->priv->comphy_regs + COMPHY_SELECTOR_PHY_REG); + new = (old & ~clr) | set; + writel(new, lane->priv->comphy_regs + COMPHY_SELECTOR_PHY_REG); + + spin_unlock_irqrestore(&lane->priv->lock, flags); + + dev_dbg(lane->dev, + "COMPHY[%d] mode[%d] changed PHY selector 0x%08x -> 0x%08x\n", + lane->id, lane->mode, old, new); + + return 0; +error: + dev_err(lane->dev, "COMPHY[%d] mode[%d] is invalid\n", lane->id, + lane->mode); + return -EINVAL; +} + +static int +mvebu_a3700_comphy_sata_power_on(struct mvebu_a3700_comphy_lane *lane) +{ + u32 mask, data, ref_clk; + int ret; + + /* Configure phy selector for SATA */ + ret = mvebu_a3700_comphy_set_phy_selector(lane); + if (ret) + return ret; + + /* Clear phy isolation mode to make it work in normal mode */ + comphy_lane_reg_set(lane, COMPHY_ISOLATION_CTRL, + 0x0, PHY_ISOLATE_MODE); + + /* 0. Check the Polarity invert bits */ + data = 0x0; + if (lane->invert_tx) + data |= TXD_INVERT_BIT; + if (lane->invert_rx) + data |= RXD_INVERT_BIT; + mask = TXD_INVERT_BIT | RXD_INVERT_BIT; + comphy_lane_reg_set(lane, COMPHY_SYNC_PATTERN, data, mask); + + /* 1. Select 40-bit data width */ + comphy_lane_reg_set(lane, COMPHY_DIG_LOOPBACK_EN, + DATA_WIDTH_40BIT, SEL_DATA_WIDTH_MASK); + + /* 2. Select reference clock(25M) and PHY mode (SATA) */ + if (lane->priv->xtal_is_40m) + ref_clk = REF_FREF_SEL_SERDES_40MHZ; + else + ref_clk = REF_FREF_SEL_SERDES_25MHZ; + + data = ref_clk | COMPHY_MODE_SATA; + mask = REF_FREF_SEL_MASK | COMPHY_MODE_MASK; + comphy_lane_reg_set(lane, COMPHY_POWER_PLL_CTRL, data, mask); + + /* 3. Use maximum PLL rate (no power save) */ + comphy_lane_reg_set(lane, COMPHY_KVCO_CAL_CTRL, + USE_MAX_PLL_RATE_BIT, USE_MAX_PLL_RATE_BIT); + + /* 4. Reset reserved bit */ + comphy_set_indirect(lane->priv, COMPHY_RESERVED_REG, + 0x0, PHYCTRL_FRM_PIN_BIT); + + /* 5. Set vendor-specific configuration (It is done in sata driver) */ + /* XXX: in U-Boot below sequence was executed in this place, in Linux + * not. Now it is done only in U-Boot before this comphy + * initialization - tests shows that it works ok, but in case of any + * future problem it is left for reference. + * reg_set(MVEBU_REGS_BASE + 0xe00a0, 0, 0xffffffff); + * reg_set(MVEBU_REGS_BASE + 0xe00a4, BIT(6), BIT(6)); + */ + + /* Wait for > 55 us to allow PLL be enabled */ + udelay(PLL_SET_DELAY_US); + + /* Polling status */ + ret = comphy_lane_reg_poll(lane, COMPHY_DIG_LOOPBACK_EN, + PLL_READY_TX_BIT, COMPHY_PLL_SLEEP, + COMPHY_PLL_TIMEOUT); + if (ret) + dev_err(lane->dev, "Failed to lock SATA PLL\n"); + + return ret; +} + +static void comphy_gbe_phy_init(struct mvebu_a3700_comphy_lane *lane, + bool is_1gbps) +{ + int addr, fix_idx; + u16 val; + + fix_idx = 0; + for (addr = 0; addr < 512; addr++) { + /* + * All PHY register values are defined in full for 3.125Gbps + * SERDES speed. The values required for 1.25 Gbps are almost + * the same and only few registers should be "fixed" in + * comparison to 3.125 Gbps values. These register values are + * stored in "gbe_phy_init_fix" array. + */ + if (!is_1gbps && gbe_phy_init_fix[fix_idx].addr == addr) { + /* Use new value */ + val = gbe_phy_init_fix[fix_idx].value; + if (fix_idx < ARRAY_SIZE(gbe_phy_init_fix)) + fix_idx++; + } else { + val = gbe_phy_init[addr]; + } + + comphy_lane_reg_set(lane, addr, val, 0xFFFF); + } +} + +static int +mvebu_a3700_comphy_ethernet_power_on(struct mvebu_a3700_comphy_lane *lane) +{ + u32 mask, data, speed_sel; + int ret; + + /* Set selector */ + ret = mvebu_a3700_comphy_set_phy_selector(lane); + if (ret) + return ret; + + /* + * 1. Reset PHY by setting PHY input port PIN_RESET=1. + * 2. Set PHY input port PIN_TX_IDLE=1, PIN_PU_IVREF=1 to keep + * PHY TXP/TXN output to idle state during PHY initialization + * 3. Set PHY input port PIN_PU_PLL=0, PIN_PU_RX=0, PIN_PU_TX=0. + */ + data = PIN_PU_IVREF_BIT | PIN_TX_IDLE_BIT | PIN_RESET_COMPHY_BIT; + mask = data | PIN_RESET_CORE_BIT | PIN_PU_PLL_BIT | PIN_PU_RX_BIT | + PIN_PU_TX_BIT | PHY_RX_INIT_BIT; + comphy_periph_reg_set(lane, COMPHY_PHY_CFG1, data, mask); + + /* 4. Release reset to the PHY by setting PIN_RESET=0. */ + data = 0x0; + mask = PIN_RESET_COMPHY_BIT; + comphy_periph_reg_set(lane, COMPHY_PHY_CFG1, data, mask); + + /* + * 5. Set PIN_PHY_GEN_TX[3:0] and PIN_PHY_GEN_RX[3:0] to decide COMPHY + * bit rate + */ + switch (lane->submode) { + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_1000BASEX: + /* SGMII 1G, SerDes speed 1.25G */ + speed_sel = SERDES_SPEED_1_25_G; + break; + case PHY_INTERFACE_MODE_2500BASEX: + /* 2500Base-X, SerDes speed 3.125G */ + speed_sel = SERDES_SPEED_3_125_G; + break; + default: + /* Other rates are not supported */ + dev_err(lane->dev, + "unsupported phy speed %d on comphy lane%d\n", + lane->submode, lane->id); + return -EINVAL; + } + data = GEN_RX_SEL_VALUE(speed_sel) | GEN_TX_SEL_VALUE(speed_sel); + mask = GEN_RX_SEL_MASK | GEN_TX_SEL_MASK; + comphy_periph_reg_set(lane, COMPHY_PHY_CFG1, data, mask); + + /* + * 6. Wait 10mS for bandgap and reference clocks to stabilize; then + * start SW programming. + */ + mdelay(10); + + /* 7. Program COMPHY register PHY_MODE */ + data = COMPHY_MODE_SERDES; + mask = COMPHY_MODE_MASK; + comphy_lane_reg_set(lane, COMPHY_POWER_PLL_CTRL, data, mask); + + /* + * 8. Set COMPHY register REFCLK_SEL to select the correct REFCLK + * source + */ + data = 0x0; + mask = PHY_REF_CLK_SEL; + comphy_lane_reg_set(lane, COMPHY_MISC_CTRL0, data, mask); + + /* + * 9. Set correct reference clock frequency in COMPHY register + * REF_FREF_SEL. + */ + if (lane->priv->xtal_is_40m) + data = REF_FREF_SEL_SERDES_50MHZ; + else + data = REF_FREF_SEL_SERDES_25MHZ; + + mask = REF_FREF_SEL_MASK; + comphy_lane_reg_set(lane, COMPHY_POWER_PLL_CTRL, data, mask); + + /* + * 10. Program COMPHY register PHY_GEN_MAX[1:0] + * This step is mentioned in the flow received from verification team. + * However the PHY_GEN_MAX value is only meaningful for other interfaces + * (not SERDES). For instance, it selects SATA speed 1.5/3/6 Gbps or + * PCIe speed 2.5/5 Gbps + */ + + /* + * 11. Program COMPHY register SEL_BITS to set correct parallel data + * bus width + */ + data = DATA_WIDTH_10BIT; + mask = SEL_DATA_WIDTH_MASK; + comphy_lane_reg_set(lane, COMPHY_DIG_LOOPBACK_EN, data, mask); + + /* + * 12. As long as DFE function needs to be enabled in any mode, + * COMPHY register DFE_UPDATE_EN[5:0] shall be programmed to 0x3F + * for real chip during COMPHY power on. + * The value of the DFE_UPDATE_EN already is 0x3F, because it is the + * default value after reset of the PHY. + */ + + /* + * 13. Program COMPHY GEN registers. + * These registers should be programmed based on the lab testing result + * to achieve optimal performance. Please contact the CEA group to get + * the related GEN table during real chip bring-up. We only required to + * run though the entire registers programming flow defined by + * "comphy_gbe_phy_init" when the REF clock is 40 MHz. For REF clock + * 25 MHz the default values stored in PHY registers are OK. + */ + dev_dbg(lane->dev, "Running C-DPI phy init %s mode\n", + lane->submode == PHY_INTERFACE_MODE_2500BASEX ? "2G5" : "1G"); + if (lane->priv->xtal_is_40m) + comphy_gbe_phy_init(lane, + lane->submode != PHY_INTERFACE_MODE_2500BASEX); + + /* + * 14. Check the PHY Polarity invert bit + */ + data = 0x0; + if (lane->invert_tx) + data |= TXD_INVERT_BIT; + if (lane->invert_rx) + data |= RXD_INVERT_BIT; + mask = TXD_INVERT_BIT | RXD_INVERT_BIT; + comphy_lane_reg_set(lane, COMPHY_SYNC_PATTERN, data, mask); + + /* + * 15. Set PHY input ports PIN_PU_PLL, PIN_PU_TX and PIN_PU_RX to 1 to + * start PHY power up sequence. All the PHY register programming should + * be done before PIN_PU_PLL=1. There should be no register programming + * for normal PHY operation from this point. + */ + data = PIN_PU_PLL_BIT | PIN_PU_RX_BIT | PIN_PU_TX_BIT; + mask = data; + comphy_periph_reg_set(lane, COMPHY_PHY_CFG1, data, mask); + + /* + * 16. Wait for PHY power up sequence to finish by checking output ports + * PIN_PLL_READY_TX=1 and PIN_PLL_READY_RX=1. + */ + ret = comphy_periph_reg_poll(lane, COMPHY_PHY_STAT1, + PHY_PLL_READY_TX_BIT | + PHY_PLL_READY_RX_BIT, + COMPHY_PLL_SLEEP, COMPHY_PLL_TIMEOUT); + if (ret) { + dev_err(lane->dev, "Failed to lock PLL for SERDES PHY %d\n", + lane->id); + return ret; + } + + /* + * 17. Set COMPHY input port PIN_TX_IDLE=0 + */ + comphy_periph_reg_set(lane, COMPHY_PHY_CFG1, 0x0, PIN_TX_IDLE_BIT); + + /* + * 18. After valid data appear on PIN_RXDATA bus, set PIN_RX_INIT=1. To + * start RX initialization. PIN_RX_INIT_DONE will be cleared to 0 by the + * PHY After RX initialization is done, PIN_RX_INIT_DONE will be set to + * 1 by COMPHY Set PIN_RX_INIT=0 after PIN_RX_INIT_DONE= 1. Please + * refer to RX initialization part for details. + */ + comphy_periph_reg_set(lane, COMPHY_PHY_CFG1, + PHY_RX_INIT_BIT, PHY_RX_INIT_BIT); + + ret = comphy_periph_reg_poll(lane, COMPHY_PHY_STAT1, + PHY_PLL_READY_TX_BIT | + PHY_PLL_READY_RX_BIT, + COMPHY_PLL_SLEEP, COMPHY_PLL_TIMEOUT); + if (ret) { + dev_err(lane->dev, "Failed to lock PLL for SERDES PHY %d\n", + lane->id); + return ret; + } + + ret = comphy_periph_reg_poll(lane, COMPHY_PHY_STAT1, + PHY_RX_INIT_DONE_BIT, + COMPHY_PLL_SLEEP, COMPHY_PLL_TIMEOUT); + if (ret) + dev_err(lane->dev, "Failed to init RX of SERDES PHY %d\n", + lane->id); + + return ret; +} + +static int +mvebu_a3700_comphy_usb3_power_on(struct mvebu_a3700_comphy_lane *lane) +{ + u32 mask, data, cfg, ref_clk; + int ret; + + /* Set phy seclector */ + ret = mvebu_a3700_comphy_set_phy_selector(lane); + if (ret) + return ret; + + /* COMPHY register reset (cleared automatically) */ + comphy_lane_reg_set(lane, COMPHY_SFT_RESET, SFT_RST, SFT_RST); + + /* + * 0. Set PHY OTG Control(0x5d034), bit 4, Power up OTG module The + * register belong to UTMI module, so it is set in UTMI phy driver. + */ + + /* + * 1. Set PRD_TXDEEMPH (3.5db de-emph) + */ + data = PRD_TXDEEMPH0_MASK; + mask = PRD_TXDEEMPH0_MASK | PRD_TXMARGIN_MASK | PRD_TXSWING_MASK | + CFG_TX_ALIGN_POS_MASK; + comphy_lane_reg_set(lane, COMPHY_PIPE_LANE_CFG0, data, mask); + + /* + * 2. Set BIT0: enable transmitter in high impedance mode + * Set BIT[3:4]: delay 2 clock cycles for HiZ off latency + * Set BIT6: Tx detect Rx at HiZ mode + * Unset BIT15: set to 0 to set USB3 De-emphasize level to -3.5db + * together with bit 0 of COMPHY_PIPE_LANE_CFG0 register + */ + data = TX_DET_RX_MODE | GEN2_TX_DATA_DLY_DEFT | TX_ELEC_IDLE_MODE_EN; + mask = PRD_TXDEEMPH1_MASK | TX_DET_RX_MODE | GEN2_TX_DATA_DLY_MASK | + TX_ELEC_IDLE_MODE_EN; + comphy_lane_reg_set(lane, COMPHY_PIPE_LANE_CFG1, data, mask); + + /* + * 3. Set Spread Spectrum Clock Enabled + */ + comphy_lane_reg_set(lane, COMPHY_PIPE_LANE_CFG4, + SPREAD_SPECTRUM_CLK_EN, SPREAD_SPECTRUM_CLK_EN); + + /* + * 4. Set Override Margining Controls From the MAC: + * Use margining signals from lane configuration + */ + comphy_lane_reg_set(lane, COMPHY_PIPE_TEST_MODE_CTRL, + MODE_MARGIN_OVERRIDE, 0xFFFF); + + /* + * 5. Set Lane-to-Lane Bundle Clock Sampling Period = per PCLK cycles + * set Mode Clock Source = PCLK is generated from REFCLK + */ + data = 0x0; + mask = MODE_CLK_SRC | BUNDLE_PERIOD_SEL | BUNDLE_PERIOD_SCALE_MASK | + BUNDLE_SAMPLE_CTRL | PLL_READY_DLY_MASK; + comphy_lane_reg_set(lane, COMPHY_PIPE_CLK_SRC_LO, data, mask); + + /* + * 6. Set G2 Spread Spectrum Clock Amplitude at 4K + */ + comphy_lane_reg_set(lane, COMPHY_GEN2_SET2, + GS2_TX_SSC_AMP_4128, GS2_TX_SSC_AMP_MASK); + + /* + * 7. Unset G3 Spread Spectrum Clock Amplitude + * set G3 TX and RX Register Master Current Select + */ + data = GS2_VREG_RXTX_MAS_ISET_60U; + mask = GS2_TX_SSC_AMP_MASK | GS2_VREG_RXTX_MAS_ISET_MASK | + GS2_RSVD_6_0_MASK; + comphy_lane_reg_set(lane, COMPHY_GEN3_SET2, data, mask); + + /* + * 8. Check crystal jumper setting and program the Power and PLL Control + * accordingly Change RX wait + */ + if (lane->priv->xtal_is_40m) { + ref_clk = REF_FREF_SEL_PCIE_USB3_40MHZ; + cfg = CFG_PM_RXDLOZ_WAIT_12_UNIT; + } else { + ref_clk = REF_FREF_SEL_PCIE_USB3_25MHZ; + cfg = CFG_PM_RXDLOZ_WAIT_7_UNIT; + } + + data = PU_IVREF_BIT | PU_PLL_BIT | PU_RX_BIT | PU_TX_BIT | + PU_TX_INTP_BIT | PU_DFE_BIT | COMPHY_MODE_USB3 | ref_clk; + mask = PU_IVREF_BIT | PU_PLL_BIT | PU_RX_BIT | PU_TX_BIT | + PU_TX_INTP_BIT | PU_DFE_BIT | PLL_LOCK_BIT | COMPHY_MODE_MASK | + REF_FREF_SEL_MASK; + comphy_lane_reg_set(lane, COMPHY_POWER_PLL_CTRL, data, mask); + + data = CFG_PM_RXDEN_WAIT_1_UNIT | cfg; + mask = CFG_PM_OSCCLK_WAIT_MASK | CFG_PM_RXDEN_WAIT_MASK | + CFG_PM_RXDLOZ_WAIT_MASK; + comphy_lane_reg_set(lane, COMPHY_PIPE_PWR_MGM_TIM1, data, mask); + + /* + * 9. Enable idle sync + */ + comphy_lane_reg_set(lane, COMPHY_IDLE_SYNC_EN, + IDLE_SYNC_EN, IDLE_SYNC_EN); + + /* + * 10. Enable the output of 500M clock + */ + comphy_lane_reg_set(lane, COMPHY_MISC_CTRL0, CLK500M_EN, CLK500M_EN); + + /* + * 11. Set 20-bit data width + */ + comphy_lane_reg_set(lane, COMPHY_DIG_LOOPBACK_EN, + DATA_WIDTH_20BIT, 0xFFFF); + + /* + * 12. Override Speed_PLL value and use MAC PLL + */ + data = SPEED_PLL_VALUE_16 | USE_MAX_PLL_RATE_BIT; + mask = 0xFFFF; + comphy_lane_reg_set(lane, COMPHY_KVCO_CAL_CTRL, data, mask); + + /* + * 13. Check the Polarity invert bit + */ + data = 0x0; + if (lane->invert_tx) + data |= TXD_INVERT_BIT; + if (lane->invert_rx) + data |= RXD_INVERT_BIT; + mask = TXD_INVERT_BIT | RXD_INVERT_BIT; + comphy_lane_reg_set(lane, COMPHY_SYNC_PATTERN, data, mask); + + /* + * 14. Set max speed generation to USB3.0 5Gbps + */ + comphy_lane_reg_set(lane, COMPHY_SYNC_MASK_GEN, + PHY_GEN_MAX_USB3_5G, PHY_GEN_MAX_MASK); + + /* + * 15. Set capacitor value for FFE gain peaking to 0xF + */ + comphy_lane_reg_set(lane, COMPHY_GEN2_SET3, + GS3_FFE_CAP_SEL_VALUE, GS3_FFE_CAP_SEL_MASK); + + /* + * 16. Release SW reset + */ + data = MODE_CORE_CLK_FREQ_SEL | MODE_PIPE_WIDTH_32 | MODE_REFDIV_BY_4; + mask = 0xFFFF; + comphy_lane_reg_set(lane, COMPHY_PIPE_RST_CLK_CTRL, data, mask); + + /* Wait for > 55 us to allow PCLK be enabled */ + udelay(PLL_SET_DELAY_US); + + ret = comphy_lane_reg_poll(lane, COMPHY_PIPE_LANE_STAT1, TXDCLK_PCLK_EN, + COMPHY_PLL_SLEEP, COMPHY_PLL_TIMEOUT); + if (ret) + dev_err(lane->dev, "Failed to lock USB3 PLL\n"); + + return ret; +} + +static int +mvebu_a3700_comphy_pcie_power_on(struct mvebu_a3700_comphy_lane *lane) +{ + u32 mask, data, ref_clk; + int ret; + + /* Configure phy selector for PCIe */ + ret = mvebu_a3700_comphy_set_phy_selector(lane); + if (ret) + return ret; + + /* 1. Enable max PLL. */ + comphy_lane_reg_set(lane, COMPHY_PIPE_LANE_CFG1, + USE_MAX_PLL_RATE_EN, USE_MAX_PLL_RATE_EN); + + /* 2. Select 20 bit SERDES interface. */ + comphy_lane_reg_set(lane, COMPHY_PIPE_CLK_SRC_LO, + CFG_SEL_20B, CFG_SEL_20B); + + /* 3. Force to use reg setting for PCIe mode */ + comphy_lane_reg_set(lane, COMPHY_MISC_CTRL1, + SEL_BITS_PCIE_FORCE, SEL_BITS_PCIE_FORCE); + + /* 4. Change RX wait */ + data = CFG_PM_RXDEN_WAIT_1_UNIT | CFG_PM_RXDLOZ_WAIT_12_UNIT; + mask = CFG_PM_OSCCLK_WAIT_MASK | CFG_PM_RXDEN_WAIT_MASK | + CFG_PM_RXDLOZ_WAIT_MASK; + comphy_lane_reg_set(lane, COMPHY_PIPE_PWR_MGM_TIM1, data, mask); + + /* 5. Enable idle sync */ + comphy_lane_reg_set(lane, COMPHY_IDLE_SYNC_EN, + IDLE_SYNC_EN, IDLE_SYNC_EN); + + /* 6. Enable the output of 100M/125M/500M clock */ + data = CLK500M_EN | TXDCLK_2X_SEL | CLK100M_125M_EN; + mask = data; + comphy_lane_reg_set(lane, COMPHY_MISC_CTRL0, data, mask); + + /* + * 7. Enable TX, PCIE global register, 0xd0074814, it is done in + * PCI-E driver + */ + + /* + * 8. Check crystal jumper setting and program the Power and PLL + * Control accordingly + */ + + if (lane->priv->xtal_is_40m) + ref_clk = REF_FREF_SEL_PCIE_USB3_40MHZ; + else + ref_clk = REF_FREF_SEL_PCIE_USB3_25MHZ; + + data = PU_IVREF_BIT | PU_PLL_BIT | PU_RX_BIT | PU_TX_BIT | + PU_TX_INTP_BIT | PU_DFE_BIT | COMPHY_MODE_PCIE | ref_clk; + mask = 0xFFFF; + comphy_lane_reg_set(lane, COMPHY_POWER_PLL_CTRL, data, mask); + + /* 9. Override Speed_PLL value and use MAC PLL */ + comphy_lane_reg_set(lane, COMPHY_KVCO_CAL_CTRL, + SPEED_PLL_VALUE_16 | USE_MAX_PLL_RATE_BIT, + 0xFFFF); + + /* 10. Check the Polarity invert bit */ + data = 0x0; + if (lane->invert_tx) + data |= TXD_INVERT_BIT; + if (lane->invert_rx) + data |= RXD_INVERT_BIT; + mask = TXD_INVERT_BIT | RXD_INVERT_BIT; + comphy_lane_reg_set(lane, COMPHY_SYNC_PATTERN, data, mask); + + /* 11. Release SW reset */ + data = MODE_CORE_CLK_FREQ_SEL | MODE_PIPE_WIDTH_32; + mask = data | PIPE_SOFT_RESET | MODE_REFDIV_MASK; + comphy_lane_reg_set(lane, COMPHY_PIPE_RST_CLK_CTRL, data, mask); + + /* Wait for > 55 us to allow PCLK be enabled */ + udelay(PLL_SET_DELAY_US); + + ret = comphy_lane_reg_poll(lane, COMPHY_PIPE_LANE_STAT1, TXDCLK_PCLK_EN, + COMPHY_PLL_SLEEP, COMPHY_PLL_TIMEOUT); + if (ret) + dev_err(lane->dev, "Failed to lock PCIE PLL\n"); + + return ret; +} + +static void +mvebu_a3700_comphy_sata_power_off(struct mvebu_a3700_comphy_lane *lane) +{ + /* Set phy isolation mode */ + comphy_lane_reg_set(lane, COMPHY_ISOLATION_CTRL, + PHY_ISOLATE_MODE, PHY_ISOLATE_MODE); + + /* Power off PLL, Tx, Rx */ + comphy_lane_reg_set(lane, COMPHY_POWER_PLL_CTRL, + 0x0, PU_PLL_BIT | PU_RX_BIT | PU_TX_BIT); +} + +static void +mvebu_a3700_comphy_ethernet_power_off(struct mvebu_a3700_comphy_lane *lane) +{ + u32 mask, data; + + data = PIN_RESET_CORE_BIT | PIN_RESET_COMPHY_BIT | PIN_PU_IVREF_BIT | + PHY_RX_INIT_BIT; + mask = data; + comphy_periph_reg_set(lane, COMPHY_PHY_CFG1, data, mask); +} + +static void +mvebu_a3700_comphy_pcie_power_off(struct mvebu_a3700_comphy_lane *lane) +{ + /* Power off PLL, Tx, Rx */ + comphy_lane_reg_set(lane, COMPHY_POWER_PLL_CTRL, + 0x0, PU_PLL_BIT | PU_RX_BIT | PU_TX_BIT); +} + +static void mvebu_a3700_comphy_usb3_power_off(struct mvebu_a3700_comphy_lane *lane) +{ + /* + * The USB3 MAC sets the USB3 PHY to low state, so we do not + * need to power off USB3 PHY again. + */ +} + +static bool mvebu_a3700_comphy_check_mode(int lane, + enum phy_mode mode, + int submode) +{ + int i, n = ARRAY_SIZE(mvebu_a3700_comphy_modes); + + /* Unused PHY mux value is 0x0 */ + if (mode == PHY_MODE_INVALID) + return false; + + for (i = 0; i < n; i++) { + if (mvebu_a3700_comphy_modes[i].lane == lane && + mvebu_a3700_comphy_modes[i].mode == mode && + mvebu_a3700_comphy_modes[i].submode == submode) + break; + } + + if (i == n) + return false; + + return true; +} + +static int mvebu_a3700_comphy_set_mode(struct phy *phy, enum phy_mode mode, + int submode) +{ + struct mvebu_a3700_comphy_lane *lane = phy_get_drvdata(phy); + + if (!mvebu_a3700_comphy_check_mode(lane->id, mode, submode)) { + dev_err(lane->dev, "invalid COMPHY mode\n"); + return -EINVAL; + } + + /* Mode cannot be changed while the PHY is powered on */ + if (phy->power_count && + (lane->mode != mode || lane->submode != submode)) + return -EBUSY; + + /* Just remember the mode, ->power_on() will do the real setup */ + lane->mode = mode; + lane->submode = submode; + + return 0; +} + +static int mvebu_a3700_comphy_power_on(struct phy *phy) +{ + struct mvebu_a3700_comphy_lane *lane = phy_get_drvdata(phy); + + if (!mvebu_a3700_comphy_check_mode(lane->id, lane->mode, + lane->submode)) { + dev_err(lane->dev, "invalid COMPHY mode\n"); + return -EINVAL; + } + + switch (lane->mode) { + case PHY_MODE_USB_HOST_SS: + dev_dbg(lane->dev, "set lane %d to USB3 host mode\n", lane->id); + return mvebu_a3700_comphy_usb3_power_on(lane); + case PHY_MODE_SATA: + dev_dbg(lane->dev, "set lane %d to SATA mode\n", lane->id); + return mvebu_a3700_comphy_sata_power_on(lane); + case PHY_MODE_ETHERNET: + dev_dbg(lane->dev, "set lane %d to Ethernet mode\n", lane->id); + return mvebu_a3700_comphy_ethernet_power_on(lane); + case PHY_MODE_PCIE: + dev_dbg(lane->dev, "set lane %d to PCIe mode\n", lane->id); + return mvebu_a3700_comphy_pcie_power_on(lane); + default: + dev_err(lane->dev, "unsupported PHY mode (%d)\n", lane->mode); + return -EOPNOTSUPP; + } +} + +static int mvebu_a3700_comphy_power_off(struct phy *phy) +{ + struct mvebu_a3700_comphy_lane *lane = phy_get_drvdata(phy); + + switch (lane->id) { + case 0: + mvebu_a3700_comphy_usb3_power_off(lane); + mvebu_a3700_comphy_ethernet_power_off(lane); + return 0; + case 1: + mvebu_a3700_comphy_pcie_power_off(lane); + mvebu_a3700_comphy_ethernet_power_off(lane); + return 0; + case 2: + mvebu_a3700_comphy_usb3_power_off(lane); + mvebu_a3700_comphy_sata_power_off(lane); + return 0; + default: + dev_err(lane->dev, "invalid COMPHY mode\n"); + return -EINVAL; + } +} + +static const struct phy_ops mvebu_a3700_comphy_ops = { + .power_on = mvebu_a3700_comphy_power_on, + .power_off = mvebu_a3700_comphy_power_off, + .set_mode = mvebu_a3700_comphy_set_mode, + .owner = THIS_MODULE, +}; + +static struct phy *mvebu_a3700_comphy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct mvebu_a3700_comphy_lane *lane; + unsigned int port; + struct phy *phy; + + phy = of_phy_simple_xlate(dev, args); + if (IS_ERR(phy)) + return phy; + + lane = phy_get_drvdata(phy); + + port = args->args[0]; + if (port != 0 && (port != 1 || lane->id != 0)) { + dev_err(lane->dev, "invalid port number %u\n", port); + return ERR_PTR(-EINVAL); + } + + lane->invert_tx = args->args[1] & BIT(0); + lane->invert_rx = args->args[1] & BIT(1); + + return phy; +} + +static int mvebu_a3700_comphy_probe(struct platform_device *pdev) +{ + struct mvebu_a3700_comphy_priv *priv; + struct phy_provider *provider; + struct device_node *child; + struct resource *res; + struct clk *clk; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->lock); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "comphy"); + priv->comphy_regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->comphy_regs)) + return PTR_ERR(priv->comphy_regs); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "lane1_pcie_gbe"); + priv->lane1_phy_regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->lane1_phy_regs)) + return PTR_ERR(priv->lane1_phy_regs); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "lane0_usb3_gbe"); + priv->lane0_phy_regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->lane0_phy_regs)) + return PTR_ERR(priv->lane0_phy_regs); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "lane2_sata_usb3"); + priv->lane2_phy_indirect = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->lane2_phy_indirect)) + return PTR_ERR(priv->lane2_phy_indirect); + + /* + * Driver needs to know if reference xtal clock is 40MHz or 25MHz. + * Old DT bindings do not have xtal clk present. So do not fail here + * and expects that default 25MHz reference clock is used. + */ + clk = clk_get(&pdev->dev, "xtal"); + if (IS_ERR(clk)) { + if (PTR_ERR(clk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_warn(&pdev->dev, "missing 'xtal' clk (%ld)\n", + PTR_ERR(clk)); + } else { + ret = clk_prepare_enable(clk); + if (ret) { + dev_warn(&pdev->dev, "enabling xtal clk failed (%d)\n", + ret); + } else { + if (clk_get_rate(clk) == 40000000) + priv->xtal_is_40m = true; + clk_disable_unprepare(clk); + } + clk_put(clk); + } + + dev_set_drvdata(&pdev->dev, priv); + + for_each_available_child_of_node(pdev->dev.of_node, child) { + struct mvebu_a3700_comphy_lane *lane; + struct phy *phy; + int ret; + u32 lane_id; + + ret = of_property_read_u32(child, "reg", &lane_id); + if (ret < 0) { + dev_err(&pdev->dev, "missing 'reg' property (%d)\n", + ret); + continue; + } + + if (lane_id >= 3) { + dev_err(&pdev->dev, "invalid 'reg' property\n"); + continue; + } + + lane = devm_kzalloc(&pdev->dev, sizeof(*lane), GFP_KERNEL); + if (!lane) { + of_node_put(child); + return -ENOMEM; + } + + phy = devm_phy_create(&pdev->dev, child, + &mvebu_a3700_comphy_ops); + if (IS_ERR(phy)) { + of_node_put(child); + return PTR_ERR(phy); + } + + lane->priv = priv; + lane->dev = &pdev->dev; + lane->mode = PHY_MODE_INVALID; + lane->submode = PHY_INTERFACE_MODE_NA; + lane->id = lane_id; + lane->invert_tx = false; + lane->invert_rx = false; + phy_set_drvdata(phy, lane); + + /* + * To avoid relying on the bootloader/firmware configuration, + * power off all comphys. + */ + mvebu_a3700_comphy_power_off(phy); + } + + provider = devm_of_phy_provider_register(&pdev->dev, + mvebu_a3700_comphy_xlate); + + return PTR_ERR_OR_ZERO(provider); +} + +static const struct of_device_id mvebu_a3700_comphy_of_match_table[] = { + { .compatible = "marvell,comphy-a3700" }, + { }, +}; +MODULE_DEVICE_TABLE(of, mvebu_a3700_comphy_of_match_table); + +static struct platform_driver mvebu_a3700_comphy_driver = { + .probe = mvebu_a3700_comphy_probe, + .driver = { + .name = "mvebu-a3700-comphy", + .of_match_table = mvebu_a3700_comphy_of_match_table, + }, +}; +module_platform_driver(mvebu_a3700_comphy_driver); + +MODULE_AUTHOR("Miquèl Raynal <miquel.raynal@bootlin.com>"); +MODULE_AUTHOR("Pali Rohár <pali@kernel.org>"); +MODULE_AUTHOR("Marek Behún <kabel@kernel.org>"); +MODULE_DESCRIPTION("Common PHY driver for A3700"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/marvell/phy-mvebu-a3700-utmi.c b/drivers/phy/marvell/phy-mvebu-a3700-utmi.c new file mode 100644 index 000000000..8834436bc --- /dev/null +++ b/drivers/phy/marvell/phy-mvebu-a3700-utmi.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Marvell + * + * Authors: + * Igal Liberman <igall@marvell.com> + * Miquèl Raynal <miquel.raynal@bootlin.com> + * + * Marvell A3700 UTMI PHY driver + */ + +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +/* Armada 3700 UTMI PHY registers */ +#define USB2_PHY_PLL_CTRL_REG0 0x0 +#define PLL_REF_DIV_OFF 0 +#define PLL_REF_DIV_MASK GENMASK(6, 0) +#define PLL_REF_DIV_5 5 +#define PLL_FB_DIV_OFF 16 +#define PLL_FB_DIV_MASK GENMASK(24, 16) +#define PLL_FB_DIV_96 96 +#define PLL_SEL_LPFR_OFF 28 +#define PLL_SEL_LPFR_MASK GENMASK(29, 28) +#define PLL_READY BIT(31) +#define USB2_PHY_CAL_CTRL 0x8 +#define PHY_PLLCAL_DONE BIT(31) +#define PHY_IMPCAL_DONE BIT(23) +#define USB2_RX_CHAN_CTRL1 0x18 +#define USB2PHY_SQCAL_DONE BIT(31) +#define USB2_PHY_OTG_CTRL 0x34 +#define PHY_PU_OTG BIT(4) +#define USB2_PHY_CHRGR_DETECT 0x38 +#define PHY_CDP_EN BIT(2) +#define PHY_DCP_EN BIT(3) +#define PHY_PD_EN BIT(4) +#define PHY_PU_CHRG_DTC BIT(5) +#define PHY_CDP_DM_AUTO BIT(7) +#define PHY_ENSWITCH_DP BIT(12) +#define PHY_ENSWITCH_DM BIT(13) + +/* Armada 3700 USB miscellaneous registers */ +#define USB2_PHY_CTRL(usb32) (usb32 ? 0x20 : 0x4) +#define RB_USB2PHY_PU BIT(0) +#define USB2_DP_PULLDN_DEV_MODE BIT(5) +#define USB2_DM_PULLDN_DEV_MODE BIT(6) +#define RB_USB2PHY_SUSPM(usb32) (usb32 ? BIT(14) : BIT(7)) + +#define PLL_LOCK_DELAY_US 10000 +#define PLL_LOCK_TIMEOUT_US 1000000 + +/** + * struct mvebu_a3700_utmi_caps - PHY capabilities + * + * @usb32: Flag indicating which PHY is in use (impacts the register map): + * - The UTMI PHY wired to the USB3/USB2 controller (otg) + * - The UTMI PHY wired to the USB2 controller (host only) + * @ops: PHY operations + */ +struct mvebu_a3700_utmi_caps { + int usb32; + const struct phy_ops *ops; +}; + +/** + * struct mvebu_a3700_utmi - PHY driver data + * + * @regs: PHY registers + * @usb_misc: Regmap with USB miscellaneous registers including PHY ones + * @caps: PHY capabilities + * @phy: PHY handle + */ +struct mvebu_a3700_utmi { + void __iomem *regs; + struct regmap *usb_misc; + const struct mvebu_a3700_utmi_caps *caps; + struct phy *phy; +}; + +static int mvebu_a3700_utmi_phy_power_on(struct phy *phy) +{ + struct mvebu_a3700_utmi *utmi = phy_get_drvdata(phy); + struct device *dev = &phy->dev; + int usb32 = utmi->caps->usb32; + int ret = 0; + u32 reg; + + /* + * Setup PLL. 40MHz clock used to be the default, being 25MHz now. + * See "PLL Settings for Typical REFCLK" table. + */ + reg = readl(utmi->regs + USB2_PHY_PLL_CTRL_REG0); + reg &= ~(PLL_REF_DIV_MASK | PLL_FB_DIV_MASK | PLL_SEL_LPFR_MASK); + reg |= (PLL_REF_DIV_5 << PLL_REF_DIV_OFF) | + (PLL_FB_DIV_96 << PLL_FB_DIV_OFF); + writel(reg, utmi->regs + USB2_PHY_PLL_CTRL_REG0); + + /* Enable PHY pull up and disable USB2 suspend */ + regmap_update_bits(utmi->usb_misc, USB2_PHY_CTRL(usb32), + RB_USB2PHY_SUSPM(usb32) | RB_USB2PHY_PU, + RB_USB2PHY_SUSPM(usb32) | RB_USB2PHY_PU); + + if (usb32) { + /* Power up OTG module */ + reg = readl(utmi->regs + USB2_PHY_OTG_CTRL); + reg |= PHY_PU_OTG; + writel(reg, utmi->regs + USB2_PHY_OTG_CTRL); + + /* Disable PHY charger detection */ + reg = readl(utmi->regs + USB2_PHY_CHRGR_DETECT); + reg &= ~(PHY_CDP_EN | PHY_DCP_EN | PHY_PD_EN | PHY_PU_CHRG_DTC | + PHY_CDP_DM_AUTO | PHY_ENSWITCH_DP | PHY_ENSWITCH_DM); + writel(reg, utmi->regs + USB2_PHY_CHRGR_DETECT); + + /* Disable PHY DP/DM pull-down (used for device mode) */ + regmap_update_bits(utmi->usb_misc, USB2_PHY_CTRL(usb32), + USB2_DP_PULLDN_DEV_MODE | + USB2_DM_PULLDN_DEV_MODE, 0); + } + + /* Wait for PLL calibration */ + ret = readl_poll_timeout(utmi->regs + USB2_PHY_CAL_CTRL, reg, + reg & PHY_PLLCAL_DONE, + PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); + if (ret) { + dev_err(dev, "Failed to end USB2 PLL calibration\n"); + return ret; + } + + /* Wait for impedance calibration */ + ret = readl_poll_timeout(utmi->regs + USB2_PHY_CAL_CTRL, reg, + reg & PHY_IMPCAL_DONE, + PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); + if (ret) { + dev_err(dev, "Failed to end USB2 impedance calibration\n"); + return ret; + } + + /* Wait for squelch calibration */ + ret = readl_poll_timeout(utmi->regs + USB2_RX_CHAN_CTRL1, reg, + reg & USB2PHY_SQCAL_DONE, + PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); + if (ret) { + dev_err(dev, "Failed to end USB2 unknown calibration\n"); + return ret; + } + + /* Wait for PLL to be locked */ + ret = readl_poll_timeout(utmi->regs + USB2_PHY_PLL_CTRL_REG0, reg, + reg & PLL_READY, + PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); + if (ret) + dev_err(dev, "Failed to lock USB2 PLL\n"); + + return ret; +} + +static int mvebu_a3700_utmi_phy_power_off(struct phy *phy) +{ + struct mvebu_a3700_utmi *utmi = phy_get_drvdata(phy); + int usb32 = utmi->caps->usb32; + u32 reg; + + /* Disable PHY pull-up and enable USB2 suspend */ + reg = readl(utmi->regs + USB2_PHY_CTRL(usb32)); + reg &= ~(RB_USB2PHY_PU | RB_USB2PHY_SUSPM(usb32)); + writel(reg, utmi->regs + USB2_PHY_CTRL(usb32)); + + /* Power down OTG module */ + if (usb32) { + reg = readl(utmi->regs + USB2_PHY_OTG_CTRL); + reg &= ~PHY_PU_OTG; + writel(reg, utmi->regs + USB2_PHY_OTG_CTRL); + } + + return 0; +} + +static const struct phy_ops mvebu_a3700_utmi_phy_ops = { + .power_on = mvebu_a3700_utmi_phy_power_on, + .power_off = mvebu_a3700_utmi_phy_power_off, + .owner = THIS_MODULE, +}; + +static const struct mvebu_a3700_utmi_caps mvebu_a3700_utmi_otg_phy_caps = { + .usb32 = true, + .ops = &mvebu_a3700_utmi_phy_ops, +}; + +static const struct mvebu_a3700_utmi_caps mvebu_a3700_utmi_host_phy_caps = { + .usb32 = false, + .ops = &mvebu_a3700_utmi_phy_ops, +}; + +static const struct of_device_id mvebu_a3700_utmi_of_match[] = { + { + .compatible = "marvell,a3700-utmi-otg-phy", + .data = &mvebu_a3700_utmi_otg_phy_caps, + }, + { + .compatible = "marvell,a3700-utmi-host-phy", + .data = &mvebu_a3700_utmi_host_phy_caps, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, mvebu_a3700_utmi_of_match); + +static int mvebu_a3700_utmi_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mvebu_a3700_utmi *utmi; + struct phy_provider *provider; + + utmi = devm_kzalloc(dev, sizeof(*utmi), GFP_KERNEL); + if (!utmi) + return -ENOMEM; + + /* Get UTMI memory region */ + utmi->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(utmi->regs)) + return PTR_ERR(utmi->regs); + + /* Get miscellaneous Host/PHY region */ + utmi->usb_misc = syscon_regmap_lookup_by_phandle(dev->of_node, + "marvell,usb-misc-reg"); + if (IS_ERR(utmi->usb_misc)) { + dev_err(dev, + "Missing USB misc purpose system controller\n"); + return PTR_ERR(utmi->usb_misc); + } + + /* Retrieve PHY capabilities */ + utmi->caps = of_device_get_match_data(dev); + + /* Instantiate the PHY */ + utmi->phy = devm_phy_create(dev, NULL, utmi->caps->ops); + if (IS_ERR(utmi->phy)) { + dev_err(dev, "Failed to create the UTMI PHY\n"); + return PTR_ERR(utmi->phy); + } + + phy_set_drvdata(utmi->phy, utmi); + + /* Ensure the PHY is powered off */ + utmi->caps->ops->power_off(utmi->phy); + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(provider); +} + +static struct platform_driver mvebu_a3700_utmi_driver = { + .probe = mvebu_a3700_utmi_phy_probe, + .driver = { + .name = "mvebu-a3700-utmi-phy", + .of_match_table = mvebu_a3700_utmi_of_match, + }, +}; +module_platform_driver(mvebu_a3700_utmi_driver); + +MODULE_AUTHOR("Igal Liberman <igall@marvell.com>"); +MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>"); +MODULE_DESCRIPTION("Marvell EBU A3700 UTMI PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/marvell/phy-mvebu-cp110-comphy.c b/drivers/phy/marvell/phy-mvebu-cp110-comphy.c new file mode 100644 index 000000000..34672e868 --- /dev/null +++ b/drivers/phy/marvell/phy-mvebu-cp110-comphy.c @@ -0,0 +1,1112 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017 Marvell + * + * Antoine Tenart <antoine.tenart@free-electrons.com> + */ + +#include <linux/arm-smccc.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/phy.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +/* Relative to priv->base */ +#define MVEBU_COMPHY_SERDES_CFG0(n) (0x0 + (n) * 0x1000) +#define MVEBU_COMPHY_SERDES_CFG0_PU_PLL BIT(1) +#define MVEBU_COMPHY_SERDES_CFG0_GEN_RX(n) ((n) << 3) +#define MVEBU_COMPHY_SERDES_CFG0_GEN_TX(n) ((n) << 7) +#define MVEBU_COMPHY_SERDES_CFG0_PU_RX BIT(11) +#define MVEBU_COMPHY_SERDES_CFG0_PU_TX BIT(12) +#define MVEBU_COMPHY_SERDES_CFG0_HALF_BUS BIT(14) +#define MVEBU_COMPHY_SERDES_CFG0_RXAUI_MODE BIT(15) +#define MVEBU_COMPHY_SERDES_CFG1(n) (0x4 + (n) * 0x1000) +#define MVEBU_COMPHY_SERDES_CFG1_RESET BIT(3) +#define MVEBU_COMPHY_SERDES_CFG1_RX_INIT BIT(4) +#define MVEBU_COMPHY_SERDES_CFG1_CORE_RESET BIT(5) +#define MVEBU_COMPHY_SERDES_CFG1_RF_RESET BIT(6) +#define MVEBU_COMPHY_SERDES_CFG2(n) (0x8 + (n) * 0x1000) +#define MVEBU_COMPHY_SERDES_CFG2_DFE_EN BIT(4) +#define MVEBU_COMPHY_SERDES_STATUS0(n) (0x18 + (n) * 0x1000) +#define MVEBU_COMPHY_SERDES_STATUS0_TX_PLL_RDY BIT(2) +#define MVEBU_COMPHY_SERDES_STATUS0_RX_PLL_RDY BIT(3) +#define MVEBU_COMPHY_SERDES_STATUS0_RX_INIT BIT(4) +#define MVEBU_COMPHY_PWRPLL_CTRL(n) (0x804 + (n) * 0x1000) +#define MVEBU_COMPHY_PWRPLL_CTRL_RFREQ(n) ((n) << 0) +#define MVEBU_COMPHY_PWRPLL_PHY_MODE(n) ((n) << 5) +#define MVEBU_COMPHY_IMP_CAL(n) (0x80c + (n) * 0x1000) +#define MVEBU_COMPHY_IMP_CAL_TX_EXT(n) ((n) << 10) +#define MVEBU_COMPHY_IMP_CAL_TX_EXT_EN BIT(15) +#define MVEBU_COMPHY_DFE_RES(n) (0x81c + (n) * 0x1000) +#define MVEBU_COMPHY_DFE_RES_FORCE_GEN_TBL BIT(15) +#define MVEBU_COMPHY_COEF(n) (0x828 + (n) * 0x1000) +#define MVEBU_COMPHY_COEF_DFE_EN BIT(14) +#define MVEBU_COMPHY_COEF_DFE_CTRL BIT(15) +#define MVEBU_COMPHY_GEN1_S0(n) (0x834 + (n) * 0x1000) +#define MVEBU_COMPHY_GEN1_S0_TX_AMP(n) ((n) << 1) +#define MVEBU_COMPHY_GEN1_S0_TX_EMPH(n) ((n) << 7) +#define MVEBU_COMPHY_GEN1_S1(n) (0x838 + (n) * 0x1000) +#define MVEBU_COMPHY_GEN1_S1_RX_MUL_PI(n) ((n) << 0) +#define MVEBU_COMPHY_GEN1_S1_RX_MUL_PF(n) ((n) << 3) +#define MVEBU_COMPHY_GEN1_S1_RX_MUL_FI(n) ((n) << 6) +#define MVEBU_COMPHY_GEN1_S1_RX_MUL_FF(n) ((n) << 8) +#define MVEBU_COMPHY_GEN1_S1_RX_DFE_EN BIT(10) +#define MVEBU_COMPHY_GEN1_S1_RX_DIV(n) ((n) << 11) +#define MVEBU_COMPHY_GEN1_S2(n) (0x8f4 + (n) * 0x1000) +#define MVEBU_COMPHY_GEN1_S2_TX_EMPH(n) ((n) << 0) +#define MVEBU_COMPHY_GEN1_S2_TX_EMPH_EN BIT(4) +#define MVEBU_COMPHY_LOOPBACK(n) (0x88c + (n) * 0x1000) +#define MVEBU_COMPHY_LOOPBACK_DBUS_WIDTH(n) ((n) << 1) +#define MVEBU_COMPHY_VDD_CAL0(n) (0x908 + (n) * 0x1000) +#define MVEBU_COMPHY_VDD_CAL0_CONT_MODE BIT(15) +#define MVEBU_COMPHY_EXT_SELV(n) (0x914 + (n) * 0x1000) +#define MVEBU_COMPHY_EXT_SELV_RX_SAMPL(n) ((n) << 5) +#define MVEBU_COMPHY_MISC_CTRL0(n) (0x93c + (n) * 0x1000) +#define MVEBU_COMPHY_MISC_CTRL0_ICP_FORCE BIT(5) +#define MVEBU_COMPHY_MISC_CTRL0_REFCLK_SEL BIT(10) +#define MVEBU_COMPHY_RX_CTRL1(n) (0x940 + (n) * 0x1000) +#define MVEBU_COMPHY_RX_CTRL1_RXCLK2X_SEL BIT(11) +#define MVEBU_COMPHY_RX_CTRL1_CLK8T_EN BIT(12) +#define MVEBU_COMPHY_SPEED_DIV(n) (0x954 + (n) * 0x1000) +#define MVEBU_COMPHY_SPEED_DIV_TX_FORCE BIT(7) +#define MVEBU_SP_CALIB(n) (0x96c + (n) * 0x1000) +#define MVEBU_SP_CALIB_SAMPLER(n) ((n) << 8) +#define MVEBU_SP_CALIB_SAMPLER_EN BIT(12) +#define MVEBU_COMPHY_TX_SLEW_RATE(n) (0x974 + (n) * 0x1000) +#define MVEBU_COMPHY_TX_SLEW_RATE_EMPH(n) ((n) << 5) +#define MVEBU_COMPHY_TX_SLEW_RATE_SLC(n) ((n) << 10) +#define MVEBU_COMPHY_DTL_CTRL(n) (0x984 + (n) * 0x1000) +#define MVEBU_COMPHY_DTL_CTRL_DTL_FLOOP_EN BIT(2) +#define MVEBU_COMPHY_FRAME_DETECT0(n) (0xa14 + (n) * 0x1000) +#define MVEBU_COMPHY_FRAME_DETECT0_PATN(n) ((n) << 7) +#define MVEBU_COMPHY_FRAME_DETECT3(n) (0xa20 + (n) * 0x1000) +#define MVEBU_COMPHY_FRAME_DETECT3_LOST_TIMEOUT_EN BIT(12) +#define MVEBU_COMPHY_DME(n) (0xa28 + (n) * 0x1000) +#define MVEBU_COMPHY_DME_ETH_MODE BIT(7) +#define MVEBU_COMPHY_TRAINING0(n) (0xa68 + (n) * 0x1000) +#define MVEBU_COMPHY_TRAINING0_P2P_HOLD BIT(15) +#define MVEBU_COMPHY_TRAINING5(n) (0xaa4 + (n) * 0x1000) +#define MVEBU_COMPHY_TRAINING5_RX_TIMER(n) ((n) << 0) +#define MVEBU_COMPHY_TX_TRAIN_PRESET(n) (0xb1c + (n) * 0x1000) +#define MVEBU_COMPHY_TX_TRAIN_PRESET_16B_AUTO_EN BIT(8) +#define MVEBU_COMPHY_TX_TRAIN_PRESET_PRBS11 BIT(9) +#define MVEBU_COMPHY_GEN1_S3(n) (0xc40 + (n) * 0x1000) +#define MVEBU_COMPHY_GEN1_S3_FBCK_SEL BIT(9) +#define MVEBU_COMPHY_GEN1_S4(n) (0xc44 + (n) * 0x1000) +#define MVEBU_COMPHY_GEN1_S4_DFE_RES(n) ((n) << 8) +#define MVEBU_COMPHY_TX_PRESET(n) (0xc68 + (n) * 0x1000) +#define MVEBU_COMPHY_TX_PRESET_INDEX(n) ((n) << 0) +#define MVEBU_COMPHY_GEN1_S5(n) (0xd38 + (n) * 0x1000) +#define MVEBU_COMPHY_GEN1_S5_ICP(n) ((n) << 0) + +/* Relative to priv->regmap */ +#define MVEBU_COMPHY_CONF1(n) (0x1000 + (n) * 0x28) +#define MVEBU_COMPHY_CONF1_PWRUP BIT(1) +#define MVEBU_COMPHY_CONF1_USB_PCIE BIT(2) /* 0: Ethernet/SATA */ +#define MVEBU_COMPHY_CONF6(n) (0x1014 + (n) * 0x28) +#define MVEBU_COMPHY_CONF6_40B BIT(18) +#define MVEBU_COMPHY_SELECTOR 0x1140 +#define MVEBU_COMPHY_SELECTOR_PHY(n) ((n) * 0x4) +#define MVEBU_COMPHY_PIPE_SELECTOR 0x1144 +#define MVEBU_COMPHY_PIPE_SELECTOR_PIPE(n) ((n) * 0x4) +#define MVEBU_COMPHY_SD1_CTRL1 0x1148 +#define MVEBU_COMPHY_SD1_CTRL1_RXAUI1_EN BIT(26) +#define MVEBU_COMPHY_SD1_CTRL1_RXAUI0_EN BIT(27) + +#define MVEBU_COMPHY_LANES 6 +#define MVEBU_COMPHY_PORTS 3 + +#define COMPHY_SIP_POWER_ON 0x82000001 +#define COMPHY_SIP_POWER_OFF 0x82000002 + +/* + * A lane is described by the following bitfields: + * [ 1- 0]: COMPHY polarity invertion + * [ 2- 7]: COMPHY speed + * [ 5-11]: COMPHY port index + * [12-16]: COMPHY mode + * [17]: Clock source + * [18-20]: PCIe width (x1, x2, x4) + */ +#define COMPHY_FW_POL_OFFSET 0 +#define COMPHY_FW_POL_MASK GENMASK(1, 0) +#define COMPHY_FW_SPEED_OFFSET 2 +#define COMPHY_FW_SPEED_MASK GENMASK(7, 2) +#define COMPHY_FW_SPEED_MAX COMPHY_FW_SPEED_MASK +#define COMPHY_FW_SPEED_1250 0 +#define COMPHY_FW_SPEED_3125 2 +#define COMPHY_FW_SPEED_5000 3 +#define COMPHY_FW_SPEED_515625 4 +#define COMPHY_FW_SPEED_103125 6 +#define COMPHY_FW_PORT_OFFSET 8 +#define COMPHY_FW_PORT_MASK GENMASK(11, 8) +#define COMPHY_FW_MODE_OFFSET 12 +#define COMPHY_FW_MODE_MASK GENMASK(16, 12) +#define COMPHY_FW_WIDTH_OFFSET 18 +#define COMPHY_FW_WIDTH_MASK GENMASK(20, 18) + +#define COMPHY_FW_PARAM_FULL(mode, port, speed, pol, width) \ + ((((pol) << COMPHY_FW_POL_OFFSET) & COMPHY_FW_POL_MASK) | \ + (((mode) << COMPHY_FW_MODE_OFFSET) & COMPHY_FW_MODE_MASK) | \ + (((port) << COMPHY_FW_PORT_OFFSET) & COMPHY_FW_PORT_MASK) | \ + (((speed) << COMPHY_FW_SPEED_OFFSET) & COMPHY_FW_SPEED_MASK) | \ + (((width) << COMPHY_FW_WIDTH_OFFSET) & COMPHY_FW_WIDTH_MASK)) + +#define COMPHY_FW_PARAM(mode, port) \ + COMPHY_FW_PARAM_FULL(mode, port, COMPHY_FW_SPEED_MAX, 0, 0) + +#define COMPHY_FW_PARAM_ETH(mode, port, speed) \ + COMPHY_FW_PARAM_FULL(mode, port, speed, 0, 0) + +#define COMPHY_FW_PARAM_PCIE(mode, port, width) \ + COMPHY_FW_PARAM_FULL(mode, port, COMPHY_FW_SPEED_5000, 0, width) + +#define COMPHY_FW_MODE_SATA 0x1 +#define COMPHY_FW_MODE_SGMII 0x2 /* SGMII 1G */ +#define COMPHY_FW_MODE_2500BASEX 0x3 /* 2500BASE-X */ +#define COMPHY_FW_MODE_USB3H 0x4 +#define COMPHY_FW_MODE_USB3D 0x5 +#define COMPHY_FW_MODE_PCIE 0x6 +#define COMPHY_FW_MODE_RXAUI 0x7 +#define COMPHY_FW_MODE_XFI 0x8 /* SFI: 0x9 (is treated like XFI) */ + +struct mvebu_comphy_conf { + enum phy_mode mode; + int submode; + unsigned lane; + unsigned port; + u32 mux; + u32 fw_mode; +}; + +#define ETH_CONF(_lane, _port, _submode, _mux, _fw) \ + { \ + .lane = _lane, \ + .port = _port, \ + .mode = PHY_MODE_ETHERNET, \ + .submode = _submode, \ + .mux = _mux, \ + .fw_mode = _fw, \ + } + +#define GEN_CONF(_lane, _port, _mode, _fw) \ + { \ + .lane = _lane, \ + .port = _port, \ + .mode = _mode, \ + .submode = PHY_INTERFACE_MODE_NA, \ + .mux = -1, \ + .fw_mode = _fw, \ + } + +static const struct mvebu_comphy_conf mvebu_comphy_cp110_modes[] = { + /* lane 0 */ + GEN_CONF(0, 0, PHY_MODE_PCIE, COMPHY_FW_MODE_PCIE), + ETH_CONF(0, 1, PHY_INTERFACE_MODE_SGMII, 0x1, COMPHY_FW_MODE_SGMII), + ETH_CONF(0, 1, PHY_INTERFACE_MODE_2500BASEX, 0x1, COMPHY_FW_MODE_2500BASEX), + GEN_CONF(0, 1, PHY_MODE_SATA, COMPHY_FW_MODE_SATA), + /* lane 1 */ + GEN_CONF(1, 0, PHY_MODE_USB_HOST_SS, COMPHY_FW_MODE_USB3H), + GEN_CONF(1, 0, PHY_MODE_USB_DEVICE_SS, COMPHY_FW_MODE_USB3D), + GEN_CONF(1, 0, PHY_MODE_SATA, COMPHY_FW_MODE_SATA), + GEN_CONF(1, 0, PHY_MODE_PCIE, COMPHY_FW_MODE_PCIE), + ETH_CONF(1, 2, PHY_INTERFACE_MODE_SGMII, 0x1, COMPHY_FW_MODE_SGMII), + ETH_CONF(1, 2, PHY_INTERFACE_MODE_2500BASEX, 0x1, COMPHY_FW_MODE_2500BASEX), + /* lane 2 */ + ETH_CONF(2, 0, PHY_INTERFACE_MODE_SGMII, 0x1, COMPHY_FW_MODE_SGMII), + ETH_CONF(2, 0, PHY_INTERFACE_MODE_2500BASEX, 0x1, COMPHY_FW_MODE_2500BASEX), + ETH_CONF(2, 0, PHY_INTERFACE_MODE_RXAUI, 0x1, COMPHY_FW_MODE_RXAUI), + ETH_CONF(2, 0, PHY_INTERFACE_MODE_5GBASER, 0x1, COMPHY_FW_MODE_XFI), + ETH_CONF(2, 0, PHY_INTERFACE_MODE_10GBASER, 0x1, COMPHY_FW_MODE_XFI), + GEN_CONF(2, 0, PHY_MODE_USB_HOST_SS, COMPHY_FW_MODE_USB3H), + GEN_CONF(2, 0, PHY_MODE_SATA, COMPHY_FW_MODE_SATA), + GEN_CONF(2, 0, PHY_MODE_PCIE, COMPHY_FW_MODE_PCIE), + /* lane 3 */ + GEN_CONF(3, 0, PHY_MODE_PCIE, COMPHY_FW_MODE_PCIE), + ETH_CONF(3, 1, PHY_INTERFACE_MODE_SGMII, 0x2, COMPHY_FW_MODE_SGMII), + ETH_CONF(3, 1, PHY_INTERFACE_MODE_2500BASEX, 0x2, COMPHY_FW_MODE_2500BASEX), + ETH_CONF(3, 1, PHY_INTERFACE_MODE_RXAUI, 0x1, COMPHY_FW_MODE_RXAUI), + GEN_CONF(3, 1, PHY_MODE_USB_HOST_SS, COMPHY_FW_MODE_USB3H), + GEN_CONF(3, 1, PHY_MODE_SATA, COMPHY_FW_MODE_SATA), + /* lane 4 */ + ETH_CONF(4, 0, PHY_INTERFACE_MODE_SGMII, 0x2, COMPHY_FW_MODE_SGMII), + ETH_CONF(4, 0, PHY_INTERFACE_MODE_2500BASEX, 0x2, COMPHY_FW_MODE_2500BASEX), + ETH_CONF(4, 0, PHY_INTERFACE_MODE_5GBASER, 0x2, COMPHY_FW_MODE_XFI), + ETH_CONF(4, 0, PHY_INTERFACE_MODE_10GBASER, 0x2, COMPHY_FW_MODE_XFI), + ETH_CONF(4, 0, PHY_INTERFACE_MODE_RXAUI, 0x2, COMPHY_FW_MODE_RXAUI), + GEN_CONF(4, 0, PHY_MODE_USB_DEVICE_SS, COMPHY_FW_MODE_USB3D), + GEN_CONF(4, 1, PHY_MODE_USB_HOST_SS, COMPHY_FW_MODE_USB3H), + GEN_CONF(4, 1, PHY_MODE_PCIE, COMPHY_FW_MODE_PCIE), + ETH_CONF(4, 1, PHY_INTERFACE_MODE_SGMII, 0x1, COMPHY_FW_MODE_SGMII), + ETH_CONF(4, 1, PHY_INTERFACE_MODE_2500BASEX, -1, COMPHY_FW_MODE_2500BASEX), + ETH_CONF(4, 1, PHY_INTERFACE_MODE_5GBASER, -1, COMPHY_FW_MODE_XFI), + ETH_CONF(4, 1, PHY_INTERFACE_MODE_10GBASER, -1, COMPHY_FW_MODE_XFI), + /* lane 5 */ + ETH_CONF(5, 1, PHY_INTERFACE_MODE_RXAUI, 0x2, COMPHY_FW_MODE_RXAUI), + GEN_CONF(5, 1, PHY_MODE_SATA, COMPHY_FW_MODE_SATA), + ETH_CONF(5, 2, PHY_INTERFACE_MODE_SGMII, 0x1, COMPHY_FW_MODE_SGMII), + ETH_CONF(5, 2, PHY_INTERFACE_MODE_2500BASEX, 0x1, COMPHY_FW_MODE_2500BASEX), + GEN_CONF(5, 2, PHY_MODE_PCIE, COMPHY_FW_MODE_PCIE), +}; + +struct mvebu_comphy_priv { + void __iomem *base; + struct regmap *regmap; + struct device *dev; + struct clk *mg_domain_clk; + struct clk *mg_core_clk; + struct clk *axi_clk; + unsigned long cp_phys; +}; + +struct mvebu_comphy_lane { + struct mvebu_comphy_priv *priv; + unsigned id; + enum phy_mode mode; + int submode; + int port; +}; + +static int mvebu_comphy_smc(unsigned long function, unsigned long phys, + unsigned long lane, unsigned long mode) +{ + struct arm_smccc_res res; + s32 ret; + + arm_smccc_smc(function, phys, lane, mode, 0, 0, 0, 0, &res); + ret = res.a0; + + switch (ret) { + case SMCCC_RET_SUCCESS: + return 0; + case SMCCC_RET_NOT_SUPPORTED: + return -EOPNOTSUPP; + default: + return -EINVAL; + } +} + +static int mvebu_comphy_get_mode(bool fw_mode, int lane, int port, + enum phy_mode mode, int submode) +{ + int i, n = ARRAY_SIZE(mvebu_comphy_cp110_modes); + /* Ignore PCIe submode: it represents the width */ + bool ignore_submode = (mode == PHY_MODE_PCIE); + const struct mvebu_comphy_conf *conf; + + /* Unused PHY mux value is 0x0 */ + if (mode == PHY_MODE_INVALID) + return 0; + + for (i = 0; i < n; i++) { + conf = &mvebu_comphy_cp110_modes[i]; + if (conf->lane == lane && + conf->port == port && + conf->mode == mode && + (conf->submode == submode || ignore_submode)) + break; + } + + if (i == n) + return -EINVAL; + + if (fw_mode) + return conf->fw_mode; + else + return conf->mux; +} + +static inline int mvebu_comphy_get_mux(int lane, int port, + enum phy_mode mode, int submode) +{ + return mvebu_comphy_get_mode(false, lane, port, mode, submode); +} + +static inline int mvebu_comphy_get_fw_mode(int lane, int port, + enum phy_mode mode, int submode) +{ + return mvebu_comphy_get_mode(true, lane, port, mode, submode); +} + +static int mvebu_comphy_ethernet_init_reset(struct mvebu_comphy_lane *lane) +{ + struct mvebu_comphy_priv *priv = lane->priv; + u32 val; + + regmap_read(priv->regmap, MVEBU_COMPHY_CONF1(lane->id), &val); + val &= ~MVEBU_COMPHY_CONF1_USB_PCIE; + val |= MVEBU_COMPHY_CONF1_PWRUP; + regmap_write(priv->regmap, MVEBU_COMPHY_CONF1(lane->id), val); + + /* Select baud rates and PLLs */ + val = readl(priv->base + MVEBU_COMPHY_SERDES_CFG0(lane->id)); + val &= ~(MVEBU_COMPHY_SERDES_CFG0_PU_PLL | + MVEBU_COMPHY_SERDES_CFG0_PU_RX | + MVEBU_COMPHY_SERDES_CFG0_PU_TX | + MVEBU_COMPHY_SERDES_CFG0_HALF_BUS | + MVEBU_COMPHY_SERDES_CFG0_GEN_RX(0xf) | + MVEBU_COMPHY_SERDES_CFG0_GEN_TX(0xf) | + MVEBU_COMPHY_SERDES_CFG0_RXAUI_MODE); + + switch (lane->submode) { + case PHY_INTERFACE_MODE_10GBASER: + val |= MVEBU_COMPHY_SERDES_CFG0_GEN_RX(0xe) | + MVEBU_COMPHY_SERDES_CFG0_GEN_TX(0xe); + break; + case PHY_INTERFACE_MODE_RXAUI: + val |= MVEBU_COMPHY_SERDES_CFG0_GEN_RX(0xb) | + MVEBU_COMPHY_SERDES_CFG0_GEN_TX(0xb) | + MVEBU_COMPHY_SERDES_CFG0_RXAUI_MODE; + break; + case PHY_INTERFACE_MODE_2500BASEX: + val |= MVEBU_COMPHY_SERDES_CFG0_GEN_RX(0x8) | + MVEBU_COMPHY_SERDES_CFG0_GEN_TX(0x8) | + MVEBU_COMPHY_SERDES_CFG0_HALF_BUS; + break; + case PHY_INTERFACE_MODE_SGMII: + val |= MVEBU_COMPHY_SERDES_CFG0_GEN_RX(0x6) | + MVEBU_COMPHY_SERDES_CFG0_GEN_TX(0x6) | + MVEBU_COMPHY_SERDES_CFG0_HALF_BUS; + break; + default: + dev_err(priv->dev, + "unsupported comphy submode (%d) on lane %d\n", + lane->submode, + lane->id); + return -ENOTSUPP; + } + + writel(val, priv->base + MVEBU_COMPHY_SERDES_CFG0(lane->id)); + + if (lane->submode == PHY_INTERFACE_MODE_RXAUI) { + regmap_read(priv->regmap, MVEBU_COMPHY_SD1_CTRL1, &val); + + switch (lane->id) { + case 2: + case 3: + val |= MVEBU_COMPHY_SD1_CTRL1_RXAUI0_EN; + break; + case 4: + case 5: + val |= MVEBU_COMPHY_SD1_CTRL1_RXAUI1_EN; + break; + default: + dev_err(priv->dev, + "RXAUI is not supported on comphy lane %d\n", + lane->id); + return -EINVAL; + } + + regmap_write(priv->regmap, MVEBU_COMPHY_SD1_CTRL1, val); + } + + /* reset */ + val = readl(priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id)); + val &= ~(MVEBU_COMPHY_SERDES_CFG1_RESET | + MVEBU_COMPHY_SERDES_CFG1_CORE_RESET | + MVEBU_COMPHY_SERDES_CFG1_RF_RESET); + writel(val, priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id)); + + /* de-assert reset */ + val = readl(priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id)); + val |= MVEBU_COMPHY_SERDES_CFG1_RESET | + MVEBU_COMPHY_SERDES_CFG1_CORE_RESET; + writel(val, priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id)); + + /* wait until clocks are ready */ + mdelay(1); + + /* exlicitly disable 40B, the bits isn't clear on reset */ + regmap_read(priv->regmap, MVEBU_COMPHY_CONF6(lane->id), &val); + val &= ~MVEBU_COMPHY_CONF6_40B; + regmap_write(priv->regmap, MVEBU_COMPHY_CONF6(lane->id), val); + + /* refclk selection */ + val = readl(priv->base + MVEBU_COMPHY_MISC_CTRL0(lane->id)); + val &= ~MVEBU_COMPHY_MISC_CTRL0_REFCLK_SEL; + if (lane->submode == PHY_INTERFACE_MODE_10GBASER) + val |= MVEBU_COMPHY_MISC_CTRL0_ICP_FORCE; + writel(val, priv->base + MVEBU_COMPHY_MISC_CTRL0(lane->id)); + + /* power and pll selection */ + val = readl(priv->base + MVEBU_COMPHY_PWRPLL_CTRL(lane->id)); + val &= ~(MVEBU_COMPHY_PWRPLL_CTRL_RFREQ(0x1f) | + MVEBU_COMPHY_PWRPLL_PHY_MODE(0x7)); + val |= MVEBU_COMPHY_PWRPLL_CTRL_RFREQ(0x1) | + MVEBU_COMPHY_PWRPLL_PHY_MODE(0x4); + writel(val, priv->base + MVEBU_COMPHY_PWRPLL_CTRL(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_LOOPBACK(lane->id)); + val &= ~MVEBU_COMPHY_LOOPBACK_DBUS_WIDTH(0x7); + val |= MVEBU_COMPHY_LOOPBACK_DBUS_WIDTH(0x1); + writel(val, priv->base + MVEBU_COMPHY_LOOPBACK(lane->id)); + + return 0; +} + +static int mvebu_comphy_init_plls(struct mvebu_comphy_lane *lane) +{ + struct mvebu_comphy_priv *priv = lane->priv; + u32 val; + + /* SERDES external config */ + val = readl(priv->base + MVEBU_COMPHY_SERDES_CFG0(lane->id)); + val |= MVEBU_COMPHY_SERDES_CFG0_PU_PLL | + MVEBU_COMPHY_SERDES_CFG0_PU_RX | + MVEBU_COMPHY_SERDES_CFG0_PU_TX; + writel(val, priv->base + MVEBU_COMPHY_SERDES_CFG0(lane->id)); + + /* check rx/tx pll */ + readl_poll_timeout(priv->base + MVEBU_COMPHY_SERDES_STATUS0(lane->id), + val, + val & (MVEBU_COMPHY_SERDES_STATUS0_RX_PLL_RDY | + MVEBU_COMPHY_SERDES_STATUS0_TX_PLL_RDY), + 1000, 150000); + if (!(val & (MVEBU_COMPHY_SERDES_STATUS0_RX_PLL_RDY | + MVEBU_COMPHY_SERDES_STATUS0_TX_PLL_RDY))) + return -ETIMEDOUT; + + /* rx init */ + val = readl(priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id)); + val |= MVEBU_COMPHY_SERDES_CFG1_RX_INIT; + writel(val, priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id)); + + /* check rx */ + readl_poll_timeout(priv->base + MVEBU_COMPHY_SERDES_STATUS0(lane->id), + val, val & MVEBU_COMPHY_SERDES_STATUS0_RX_INIT, + 1000, 10000); + if (!(val & MVEBU_COMPHY_SERDES_STATUS0_RX_INIT)) + return -ETIMEDOUT; + + val = readl(priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id)); + val &= ~MVEBU_COMPHY_SERDES_CFG1_RX_INIT; + writel(val, priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id)); + + return 0; +} + +static int mvebu_comphy_set_mode_sgmii(struct phy *phy) +{ + struct mvebu_comphy_lane *lane = phy_get_drvdata(phy); + struct mvebu_comphy_priv *priv = lane->priv; + u32 val; + int err; + + err = mvebu_comphy_ethernet_init_reset(lane); + if (err) + return err; + + val = readl(priv->base + MVEBU_COMPHY_RX_CTRL1(lane->id)); + val &= ~MVEBU_COMPHY_RX_CTRL1_CLK8T_EN; + val |= MVEBU_COMPHY_RX_CTRL1_RXCLK2X_SEL; + writel(val, priv->base + MVEBU_COMPHY_RX_CTRL1(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_DTL_CTRL(lane->id)); + val &= ~MVEBU_COMPHY_DTL_CTRL_DTL_FLOOP_EN; + writel(val, priv->base + MVEBU_COMPHY_DTL_CTRL(lane->id)); + + regmap_read(priv->regmap, MVEBU_COMPHY_CONF1(lane->id), &val); + val &= ~MVEBU_COMPHY_CONF1_USB_PCIE; + val |= MVEBU_COMPHY_CONF1_PWRUP; + regmap_write(priv->regmap, MVEBU_COMPHY_CONF1(lane->id), val); + + val = readl(priv->base + MVEBU_COMPHY_GEN1_S0(lane->id)); + val &= ~MVEBU_COMPHY_GEN1_S0_TX_EMPH(0xf); + val |= MVEBU_COMPHY_GEN1_S0_TX_EMPH(0x1); + writel(val, priv->base + MVEBU_COMPHY_GEN1_S0(lane->id)); + + return mvebu_comphy_init_plls(lane); +} + +static int mvebu_comphy_set_mode_rxaui(struct phy *phy) +{ + struct mvebu_comphy_lane *lane = phy_get_drvdata(phy); + struct mvebu_comphy_priv *priv = lane->priv; + u32 val; + int err; + + err = mvebu_comphy_ethernet_init_reset(lane); + if (err) + return err; + + val = readl(priv->base + MVEBU_COMPHY_RX_CTRL1(lane->id)); + val |= MVEBU_COMPHY_RX_CTRL1_RXCLK2X_SEL | + MVEBU_COMPHY_RX_CTRL1_CLK8T_EN; + writel(val, priv->base + MVEBU_COMPHY_RX_CTRL1(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_DTL_CTRL(lane->id)); + val |= MVEBU_COMPHY_DTL_CTRL_DTL_FLOOP_EN; + writel(val, priv->base + MVEBU_COMPHY_DTL_CTRL(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_SERDES_CFG2(lane->id)); + val |= MVEBU_COMPHY_SERDES_CFG2_DFE_EN; + writel(val, priv->base + MVEBU_COMPHY_SERDES_CFG2(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_DFE_RES(lane->id)); + val |= MVEBU_COMPHY_DFE_RES_FORCE_GEN_TBL; + writel(val, priv->base + MVEBU_COMPHY_DFE_RES(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_GEN1_S0(lane->id)); + val &= ~MVEBU_COMPHY_GEN1_S0_TX_EMPH(0xf); + val |= MVEBU_COMPHY_GEN1_S0_TX_EMPH(0xd); + writel(val, priv->base + MVEBU_COMPHY_GEN1_S0(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_GEN1_S1(lane->id)); + val &= ~(MVEBU_COMPHY_GEN1_S1_RX_MUL_PI(0x7) | + MVEBU_COMPHY_GEN1_S1_RX_MUL_PF(0x7)); + val |= MVEBU_COMPHY_GEN1_S1_RX_MUL_PI(0x1) | + MVEBU_COMPHY_GEN1_S1_RX_MUL_PF(0x1) | + MVEBU_COMPHY_GEN1_S1_RX_DFE_EN; + writel(val, priv->base + MVEBU_COMPHY_GEN1_S1(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_COEF(lane->id)); + val &= ~(MVEBU_COMPHY_COEF_DFE_EN | MVEBU_COMPHY_COEF_DFE_CTRL); + writel(val, priv->base + MVEBU_COMPHY_COEF(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_GEN1_S4(lane->id)); + val &= ~MVEBU_COMPHY_GEN1_S4_DFE_RES(0x3); + val |= MVEBU_COMPHY_GEN1_S4_DFE_RES(0x1); + writel(val, priv->base + MVEBU_COMPHY_GEN1_S4(lane->id)); + + return mvebu_comphy_init_plls(lane); +} + +static int mvebu_comphy_set_mode_10gbaser(struct phy *phy) +{ + struct mvebu_comphy_lane *lane = phy_get_drvdata(phy); + struct mvebu_comphy_priv *priv = lane->priv; + u32 val; + int err; + + err = mvebu_comphy_ethernet_init_reset(lane); + if (err) + return err; + + val = readl(priv->base + MVEBU_COMPHY_RX_CTRL1(lane->id)); + val |= MVEBU_COMPHY_RX_CTRL1_RXCLK2X_SEL | + MVEBU_COMPHY_RX_CTRL1_CLK8T_EN; + writel(val, priv->base + MVEBU_COMPHY_RX_CTRL1(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_DTL_CTRL(lane->id)); + val |= MVEBU_COMPHY_DTL_CTRL_DTL_FLOOP_EN; + writel(val, priv->base + MVEBU_COMPHY_DTL_CTRL(lane->id)); + + /* Speed divider */ + val = readl(priv->base + MVEBU_COMPHY_SPEED_DIV(lane->id)); + val |= MVEBU_COMPHY_SPEED_DIV_TX_FORCE; + writel(val, priv->base + MVEBU_COMPHY_SPEED_DIV(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_SERDES_CFG2(lane->id)); + val |= MVEBU_COMPHY_SERDES_CFG2_DFE_EN; + writel(val, priv->base + MVEBU_COMPHY_SERDES_CFG2(lane->id)); + + /* DFE resolution */ + val = readl(priv->base + MVEBU_COMPHY_DFE_RES(lane->id)); + val |= MVEBU_COMPHY_DFE_RES_FORCE_GEN_TBL; + writel(val, priv->base + MVEBU_COMPHY_DFE_RES(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_GEN1_S0(lane->id)); + val &= ~(MVEBU_COMPHY_GEN1_S0_TX_AMP(0x1f) | + MVEBU_COMPHY_GEN1_S0_TX_EMPH(0xf)); + val |= MVEBU_COMPHY_GEN1_S0_TX_AMP(0x1c) | + MVEBU_COMPHY_GEN1_S0_TX_EMPH(0xe); + writel(val, priv->base + MVEBU_COMPHY_GEN1_S0(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_GEN1_S2(lane->id)); + val &= ~MVEBU_COMPHY_GEN1_S2_TX_EMPH(0xf); + val |= MVEBU_COMPHY_GEN1_S2_TX_EMPH_EN; + writel(val, priv->base + MVEBU_COMPHY_GEN1_S2(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_TX_SLEW_RATE(lane->id)); + val |= MVEBU_COMPHY_TX_SLEW_RATE_EMPH(0x3) | + MVEBU_COMPHY_TX_SLEW_RATE_SLC(0x3f); + writel(val, priv->base + MVEBU_COMPHY_TX_SLEW_RATE(lane->id)); + + /* Impedance calibration */ + val = readl(priv->base + MVEBU_COMPHY_IMP_CAL(lane->id)); + val &= ~MVEBU_COMPHY_IMP_CAL_TX_EXT(0x1f); + val |= MVEBU_COMPHY_IMP_CAL_TX_EXT(0xe) | + MVEBU_COMPHY_IMP_CAL_TX_EXT_EN; + writel(val, priv->base + MVEBU_COMPHY_IMP_CAL(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_GEN1_S5(lane->id)); + val &= ~MVEBU_COMPHY_GEN1_S5_ICP(0xf); + writel(val, priv->base + MVEBU_COMPHY_GEN1_S5(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_GEN1_S1(lane->id)); + val &= ~(MVEBU_COMPHY_GEN1_S1_RX_MUL_PI(0x7) | + MVEBU_COMPHY_GEN1_S1_RX_MUL_PF(0x7) | + MVEBU_COMPHY_GEN1_S1_RX_MUL_FI(0x3) | + MVEBU_COMPHY_GEN1_S1_RX_MUL_FF(0x3)); + val |= MVEBU_COMPHY_GEN1_S1_RX_DFE_EN | + MVEBU_COMPHY_GEN1_S1_RX_MUL_PI(0x2) | + MVEBU_COMPHY_GEN1_S1_RX_MUL_PF(0x2) | + MVEBU_COMPHY_GEN1_S1_RX_MUL_FF(0x1) | + MVEBU_COMPHY_GEN1_S1_RX_DIV(0x3); + writel(val, priv->base + MVEBU_COMPHY_GEN1_S1(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_COEF(lane->id)); + val &= ~(MVEBU_COMPHY_COEF_DFE_EN | MVEBU_COMPHY_COEF_DFE_CTRL); + writel(val, priv->base + MVEBU_COMPHY_COEF(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_GEN1_S4(lane->id)); + val &= ~MVEBU_COMPHY_GEN1_S4_DFE_RES(0x3); + val |= MVEBU_COMPHY_GEN1_S4_DFE_RES(0x1); + writel(val, priv->base + MVEBU_COMPHY_GEN1_S4(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_GEN1_S3(lane->id)); + val |= MVEBU_COMPHY_GEN1_S3_FBCK_SEL; + writel(val, priv->base + MVEBU_COMPHY_GEN1_S3(lane->id)); + + /* rx training timer */ + val = readl(priv->base + MVEBU_COMPHY_TRAINING5(lane->id)); + val &= ~MVEBU_COMPHY_TRAINING5_RX_TIMER(0x3ff); + val |= MVEBU_COMPHY_TRAINING5_RX_TIMER(0x13); + writel(val, priv->base + MVEBU_COMPHY_TRAINING5(lane->id)); + + /* tx train peak to peak hold */ + val = readl(priv->base + MVEBU_COMPHY_TRAINING0(lane->id)); + val |= MVEBU_COMPHY_TRAINING0_P2P_HOLD; + writel(val, priv->base + MVEBU_COMPHY_TRAINING0(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_TX_PRESET(lane->id)); + val &= ~MVEBU_COMPHY_TX_PRESET_INDEX(0xf); + val |= MVEBU_COMPHY_TX_PRESET_INDEX(0x2); /* preset coeff */ + writel(val, priv->base + MVEBU_COMPHY_TX_PRESET(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_FRAME_DETECT3(lane->id)); + val &= ~MVEBU_COMPHY_FRAME_DETECT3_LOST_TIMEOUT_EN; + writel(val, priv->base + MVEBU_COMPHY_FRAME_DETECT3(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_TX_TRAIN_PRESET(lane->id)); + val |= MVEBU_COMPHY_TX_TRAIN_PRESET_16B_AUTO_EN | + MVEBU_COMPHY_TX_TRAIN_PRESET_PRBS11; + writel(val, priv->base + MVEBU_COMPHY_TX_TRAIN_PRESET(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_FRAME_DETECT0(lane->id)); + val &= ~MVEBU_COMPHY_FRAME_DETECT0_PATN(0x1ff); + val |= MVEBU_COMPHY_FRAME_DETECT0_PATN(0x88); + writel(val, priv->base + MVEBU_COMPHY_FRAME_DETECT0(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_DME(lane->id)); + val |= MVEBU_COMPHY_DME_ETH_MODE; + writel(val, priv->base + MVEBU_COMPHY_DME(lane->id)); + + val = readl(priv->base + MVEBU_COMPHY_VDD_CAL0(lane->id)); + val |= MVEBU_COMPHY_VDD_CAL0_CONT_MODE; + writel(val, priv->base + MVEBU_COMPHY_VDD_CAL0(lane->id)); + + val = readl(priv->base + MVEBU_SP_CALIB(lane->id)); + val &= ~MVEBU_SP_CALIB_SAMPLER(0x3); + val |= MVEBU_SP_CALIB_SAMPLER(0x3) | + MVEBU_SP_CALIB_SAMPLER_EN; + writel(val, priv->base + MVEBU_SP_CALIB(lane->id)); + val &= ~MVEBU_SP_CALIB_SAMPLER_EN; + writel(val, priv->base + MVEBU_SP_CALIB(lane->id)); + + /* External rx regulator */ + val = readl(priv->base + MVEBU_COMPHY_EXT_SELV(lane->id)); + val &= ~MVEBU_COMPHY_EXT_SELV_RX_SAMPL(0x1f); + val |= MVEBU_COMPHY_EXT_SELV_RX_SAMPL(0x1a); + writel(val, priv->base + MVEBU_COMPHY_EXT_SELV(lane->id)); + + return mvebu_comphy_init_plls(lane); +} + +static int mvebu_comphy_power_on_legacy(struct phy *phy) +{ + struct mvebu_comphy_lane *lane = phy_get_drvdata(phy); + struct mvebu_comphy_priv *priv = lane->priv; + int ret, mux; + u32 val; + + mux = mvebu_comphy_get_mux(lane->id, lane->port, + lane->mode, lane->submode); + if (mux < 0) + return -ENOTSUPP; + + regmap_read(priv->regmap, MVEBU_COMPHY_PIPE_SELECTOR, &val); + val &= ~(0xf << MVEBU_COMPHY_PIPE_SELECTOR_PIPE(lane->id)); + regmap_write(priv->regmap, MVEBU_COMPHY_PIPE_SELECTOR, val); + + regmap_read(priv->regmap, MVEBU_COMPHY_SELECTOR, &val); + val &= ~(0xf << MVEBU_COMPHY_SELECTOR_PHY(lane->id)); + val |= mux << MVEBU_COMPHY_SELECTOR_PHY(lane->id); + regmap_write(priv->regmap, MVEBU_COMPHY_SELECTOR, val); + + switch (lane->submode) { + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_2500BASEX: + ret = mvebu_comphy_set_mode_sgmii(phy); + break; + case PHY_INTERFACE_MODE_RXAUI: + ret = mvebu_comphy_set_mode_rxaui(phy); + break; + case PHY_INTERFACE_MODE_10GBASER: + ret = mvebu_comphy_set_mode_10gbaser(phy); + break; + default: + return -ENOTSUPP; + } + + /* digital reset */ + val = readl(priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id)); + val |= MVEBU_COMPHY_SERDES_CFG1_RF_RESET; + writel(val, priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id)); + + return ret; +} + +static int mvebu_comphy_power_on(struct phy *phy) +{ + struct mvebu_comphy_lane *lane = phy_get_drvdata(phy); + struct mvebu_comphy_priv *priv = lane->priv; + int fw_mode, fw_speed; + u32 fw_param = 0; + int ret; + + fw_mode = mvebu_comphy_get_fw_mode(lane->id, lane->port, + lane->mode, lane->submode); + if (fw_mode < 0) + goto try_legacy; + + /* Try SMC flow first */ + switch (lane->mode) { + case PHY_MODE_ETHERNET: + switch (lane->submode) { + case PHY_INTERFACE_MODE_RXAUI: + dev_dbg(priv->dev, "set lane %d to RXAUI mode\n", + lane->id); + fw_speed = 0; + break; + case PHY_INTERFACE_MODE_SGMII: + dev_dbg(priv->dev, "set lane %d to 1000BASE-X mode\n", + lane->id); + fw_speed = COMPHY_FW_SPEED_1250; + break; + case PHY_INTERFACE_MODE_2500BASEX: + dev_dbg(priv->dev, "set lane %d to 2500BASE-X mode\n", + lane->id); + fw_speed = COMPHY_FW_SPEED_3125; + break; + case PHY_INTERFACE_MODE_5GBASER: + dev_dbg(priv->dev, "set lane %d to 5GBASE-R mode\n", + lane->id); + fw_speed = COMPHY_FW_SPEED_515625; + break; + case PHY_INTERFACE_MODE_10GBASER: + dev_dbg(priv->dev, "set lane %d to 10GBASE-R mode\n", + lane->id); + fw_speed = COMPHY_FW_SPEED_103125; + break; + default: + dev_err(priv->dev, "unsupported Ethernet mode (%d)\n", + lane->submode); + return -ENOTSUPP; + } + fw_param = COMPHY_FW_PARAM_ETH(fw_mode, lane->port, fw_speed); + break; + case PHY_MODE_USB_HOST_SS: + case PHY_MODE_USB_DEVICE_SS: + dev_dbg(priv->dev, "set lane %d to USB3 mode\n", lane->id); + fw_param = COMPHY_FW_PARAM(fw_mode, lane->port); + break; + case PHY_MODE_SATA: + dev_dbg(priv->dev, "set lane %d to SATA mode\n", lane->id); + fw_param = COMPHY_FW_PARAM(fw_mode, lane->port); + break; + case PHY_MODE_PCIE: + dev_dbg(priv->dev, "set lane %d to PCIe mode (x%d)\n", lane->id, + lane->submode); + fw_param = COMPHY_FW_PARAM_PCIE(fw_mode, lane->port, + lane->submode); + break; + default: + dev_err(priv->dev, "unsupported PHY mode (%d)\n", lane->mode); + return -ENOTSUPP; + } + + ret = mvebu_comphy_smc(COMPHY_SIP_POWER_ON, priv->cp_phys, lane->id, + fw_param); + if (!ret) + return ret; + + if (ret == -EOPNOTSUPP) + dev_err(priv->dev, + "unsupported SMC call, try updating your firmware\n"); + + dev_warn(priv->dev, + "Firmware could not configure PHY %d with mode %d (ret: %d), trying legacy method\n", + lane->id, lane->mode, ret); + +try_legacy: + /* Fallback to Linux's implementation */ + return mvebu_comphy_power_on_legacy(phy); +} + +static int mvebu_comphy_set_mode(struct phy *phy, + enum phy_mode mode, int submode) +{ + struct mvebu_comphy_lane *lane = phy_get_drvdata(phy); + + if (submode == PHY_INTERFACE_MODE_1000BASEX) + submode = PHY_INTERFACE_MODE_SGMII; + + if (mvebu_comphy_get_fw_mode(lane->id, lane->port, mode, submode) < 0) + return -EINVAL; + + lane->mode = mode; + lane->submode = submode; + + /* PCIe submode represents the width */ + if (mode == PHY_MODE_PCIE && !lane->submode) + lane->submode = 1; + + return 0; +} + +static int mvebu_comphy_power_off_legacy(struct phy *phy) +{ + struct mvebu_comphy_lane *lane = phy_get_drvdata(phy); + struct mvebu_comphy_priv *priv = lane->priv; + u32 val; + + val = readl(priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id)); + val &= ~(MVEBU_COMPHY_SERDES_CFG1_RESET | + MVEBU_COMPHY_SERDES_CFG1_CORE_RESET | + MVEBU_COMPHY_SERDES_CFG1_RF_RESET); + writel(val, priv->base + MVEBU_COMPHY_SERDES_CFG1(lane->id)); + + regmap_read(priv->regmap, MVEBU_COMPHY_SELECTOR, &val); + val &= ~(0xf << MVEBU_COMPHY_SELECTOR_PHY(lane->id)); + regmap_write(priv->regmap, MVEBU_COMPHY_SELECTOR, val); + + regmap_read(priv->regmap, MVEBU_COMPHY_PIPE_SELECTOR, &val); + val &= ~(0xf << MVEBU_COMPHY_PIPE_SELECTOR_PIPE(lane->id)); + regmap_write(priv->regmap, MVEBU_COMPHY_PIPE_SELECTOR, val); + + return 0; +} + +static int mvebu_comphy_power_off(struct phy *phy) +{ + struct mvebu_comphy_lane *lane = phy_get_drvdata(phy); + struct mvebu_comphy_priv *priv = lane->priv; + int ret; + + ret = mvebu_comphy_smc(COMPHY_SIP_POWER_OFF, priv->cp_phys, + lane->id, 0); + if (!ret) + return ret; + + /* Fallback to Linux's implementation */ + return mvebu_comphy_power_off_legacy(phy); +} + +static const struct phy_ops mvebu_comphy_ops = { + .power_on = mvebu_comphy_power_on, + .power_off = mvebu_comphy_power_off, + .set_mode = mvebu_comphy_set_mode, + .owner = THIS_MODULE, +}; + +static struct phy *mvebu_comphy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct mvebu_comphy_lane *lane; + struct phy *phy; + + if (WARN_ON(args->args[0] >= MVEBU_COMPHY_PORTS)) + return ERR_PTR(-EINVAL); + + phy = of_phy_simple_xlate(dev, args); + if (IS_ERR(phy)) + return phy; + + lane = phy_get_drvdata(phy); + lane->port = args->args[0]; + + return phy; +} + +static int mvebu_comphy_init_clks(struct mvebu_comphy_priv *priv) +{ + int ret; + + priv->mg_domain_clk = devm_clk_get(priv->dev, "mg_clk"); + if (IS_ERR(priv->mg_domain_clk)) + return PTR_ERR(priv->mg_domain_clk); + + ret = clk_prepare_enable(priv->mg_domain_clk); + if (ret < 0) + return ret; + + priv->mg_core_clk = devm_clk_get(priv->dev, "mg_core_clk"); + if (IS_ERR(priv->mg_core_clk)) { + ret = PTR_ERR(priv->mg_core_clk); + goto dis_mg_domain_clk; + } + + ret = clk_prepare_enable(priv->mg_core_clk); + if (ret < 0) + goto dis_mg_domain_clk; + + priv->axi_clk = devm_clk_get(priv->dev, "axi_clk"); + if (IS_ERR(priv->axi_clk)) { + ret = PTR_ERR(priv->axi_clk); + goto dis_mg_core_clk; + } + + ret = clk_prepare_enable(priv->axi_clk); + if (ret < 0) + goto dis_mg_core_clk; + + return 0; + +dis_mg_core_clk: + clk_disable_unprepare(priv->mg_core_clk); + +dis_mg_domain_clk: + clk_disable_unprepare(priv->mg_domain_clk); + + priv->mg_domain_clk = NULL; + priv->mg_core_clk = NULL; + priv->axi_clk = NULL; + + return ret; +}; + +static void mvebu_comphy_disable_unprepare_clks(struct mvebu_comphy_priv *priv) +{ + if (priv->axi_clk) + clk_disable_unprepare(priv->axi_clk); + + if (priv->mg_core_clk) + clk_disable_unprepare(priv->mg_core_clk); + + if (priv->mg_domain_clk) + clk_disable_unprepare(priv->mg_domain_clk); +} + +static int mvebu_comphy_probe(struct platform_device *pdev) +{ + struct mvebu_comphy_priv *priv; + struct phy_provider *provider; + struct device_node *child; + struct resource *res; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = &pdev->dev; + priv->regmap = + syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "marvell,system-controller"); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + /* + * Ignore error if clocks have not been initialized properly for DT + * compatibility reasons. + */ + ret = mvebu_comphy_init_clks(priv); + if (ret) { + if (ret == -EPROBE_DEFER) + return ret; + dev_warn(&pdev->dev, "cannot initialize clocks\n"); + } + + /* + * Hack to retrieve a physical offset relative to this CP that will be + * given to the firmware + */ + priv->cp_phys = res->start; + + for_each_available_child_of_node(pdev->dev.of_node, child) { + struct mvebu_comphy_lane *lane; + struct phy *phy; + u32 val; + + ret = of_property_read_u32(child, "reg", &val); + if (ret < 0) { + dev_err(&pdev->dev, "missing 'reg' property (%d)\n", + ret); + continue; + } + + if (val >= MVEBU_COMPHY_LANES) { + dev_err(&pdev->dev, "invalid 'reg' property\n"); + continue; + } + + lane = devm_kzalloc(&pdev->dev, sizeof(*lane), GFP_KERNEL); + if (!lane) { + of_node_put(child); + ret = -ENOMEM; + goto disable_clks; + } + + phy = devm_phy_create(&pdev->dev, child, &mvebu_comphy_ops); + if (IS_ERR(phy)) { + of_node_put(child); + ret = PTR_ERR(phy); + goto disable_clks; + } + + lane->priv = priv; + lane->mode = PHY_MODE_INVALID; + lane->submode = PHY_INTERFACE_MODE_NA; + lane->id = val; + lane->port = -1; + phy_set_drvdata(phy, lane); + + /* + * All modes are supported in this driver so we could call + * mvebu_comphy_power_off(phy) here to avoid relying on the + * bootloader/firmware configuration, but for compatibility + * reasons we cannot de-configure the COMPHY without being sure + * that the firmware is up-to-date and fully-featured. + */ + } + + dev_set_drvdata(&pdev->dev, priv); + provider = devm_of_phy_provider_register(&pdev->dev, + mvebu_comphy_xlate); + + return PTR_ERR_OR_ZERO(provider); + +disable_clks: + mvebu_comphy_disable_unprepare_clks(priv); + + return ret; +} + +static const struct of_device_id mvebu_comphy_of_match_table[] = { + { .compatible = "marvell,comphy-cp110" }, + { }, +}; +MODULE_DEVICE_TABLE(of, mvebu_comphy_of_match_table); + +static struct platform_driver mvebu_comphy_driver = { + .probe = mvebu_comphy_probe, + .driver = { + .name = "mvebu-comphy", + .of_match_table = mvebu_comphy_of_match_table, + }, +}; +module_platform_driver(mvebu_comphy_driver); + +MODULE_AUTHOR("Antoine Tenart <antoine.tenart@free-electrons.com>"); +MODULE_DESCRIPTION("Common PHY driver for mvebu SoCs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/marvell/phy-mvebu-cp110-utmi.c b/drivers/phy/marvell/phy-mvebu-cp110-utmi.c new file mode 100644 index 000000000..aa27c7994 --- /dev/null +++ b/drivers/phy/marvell/phy-mvebu-cp110-utmi.c @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Marvell + * + * Authors: + * Konstantin Porotchkin <kostap@marvell.com> + * + * Marvell CP110 UTMI PHY driver + */ + +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/usb/of.h> +#include <linux/usb/otg.h> + +#define UTMI_PHY_PORTS 2 + +/* CP110 UTMI register macro definetions */ +#define SYSCON_USB_CFG_REG 0x420 +#define USB_CFG_DEVICE_EN_MASK BIT(0) +#define USB_CFG_DEVICE_MUX_OFFSET 1 +#define USB_CFG_DEVICE_MUX_MASK BIT(1) +#define USB_CFG_PLL_MASK BIT(25) + +#define SYSCON_UTMI_CFG_REG(id) (0x440 + (id) * 4) +#define UTMI_PHY_CFG_PU_MASK BIT(5) + +#define UTMI_PLL_CTRL_REG 0x0 +#define PLL_REFDIV_OFFSET 0 +#define PLL_REFDIV_MASK GENMASK(6, 0) +#define PLL_REFDIV_VAL 0x5 +#define PLL_FBDIV_OFFSET 16 +#define PLL_FBDIV_MASK GENMASK(24, 16) +#define PLL_FBDIV_VAL 0x60 +#define PLL_SEL_LPFR_MASK GENMASK(29, 28) +#define PLL_RDY BIT(31) +#define UTMI_CAL_CTRL_REG 0x8 +#define IMPCAL_VTH_OFFSET 8 +#define IMPCAL_VTH_MASK GENMASK(10, 8) +#define IMPCAL_VTH_VAL 0x7 +#define IMPCAL_DONE BIT(23) +#define PLLCAL_DONE BIT(31) +#define UTMI_TX_CH_CTRL_REG 0xC +#define DRV_EN_LS_OFFSET 12 +#define DRV_EN_LS_MASK GENMASK(15, 12) +#define IMP_SEL_LS_OFFSET 16 +#define IMP_SEL_LS_MASK GENMASK(19, 16) +#define TX_AMP_OFFSET 20 +#define TX_AMP_MASK GENMASK(22, 20) +#define TX_AMP_VAL 0x4 +#define UTMI_RX_CH_CTRL0_REG 0x14 +#define SQ_DET_EN BIT(15) +#define SQ_ANA_DTC_SEL BIT(28) +#define UTMI_RX_CH_CTRL1_REG 0x18 +#define SQ_AMP_CAL_OFFSET 0 +#define SQ_AMP_CAL_MASK GENMASK(2, 0) +#define SQ_AMP_CAL_VAL 1 +#define SQ_AMP_CAL_EN BIT(3) +#define UTMI_CTRL_STATUS0_REG 0x24 +#define SUSPENDM BIT(22) +#define TEST_SEL BIT(25) +#define UTMI_CHGDTC_CTRL_REG 0x38 +#define VDAT_OFFSET 8 +#define VDAT_MASK GENMASK(9, 8) +#define VDAT_VAL 1 +#define VSRC_OFFSET 10 +#define VSRC_MASK GENMASK(11, 10) +#define VSRC_VAL 1 + +#define PLL_LOCK_DELAY_US 10000 +#define PLL_LOCK_TIMEOUT_US 1000000 + +#define PORT_REGS(p) ((p)->priv->regs + (p)->id * 0x1000) + +/** + * struct mvebu_cp110_utmi - PHY driver data + * + * @regs: PHY registers + * @syscon: Regmap with system controller registers + * @dev: device driver handle + * @ops: phy ops + */ +struct mvebu_cp110_utmi { + void __iomem *regs; + struct regmap *syscon; + struct device *dev; + const struct phy_ops *ops; +}; + +/** + * struct mvebu_cp110_utmi_port - PHY port data + * + * @priv: PHY driver data + * @id: PHY port ID + * @dr_mode: PHY connection: USB_DR_MODE_HOST or USB_DR_MODE_PERIPHERAL + */ +struct mvebu_cp110_utmi_port { + struct mvebu_cp110_utmi *priv; + u32 id; + enum usb_dr_mode dr_mode; +}; + +static void mvebu_cp110_utmi_port_setup(struct mvebu_cp110_utmi_port *port) +{ + u32 reg; + + /* + * Setup PLL. + * The reference clock is the frequency of quartz resonator + * connected to pins REFCLK_XIN and REFCLK_XOUT of the SoC. + * Register init values are matching the 40MHz default clock. + * The crystal used for all platform boards is now 25MHz. + * See the functional specification for details. + */ + reg = readl(PORT_REGS(port) + UTMI_PLL_CTRL_REG); + reg &= ~(PLL_REFDIV_MASK | PLL_FBDIV_MASK | PLL_SEL_LPFR_MASK); + reg |= (PLL_REFDIV_VAL << PLL_REFDIV_OFFSET) | + (PLL_FBDIV_VAL << PLL_FBDIV_OFFSET); + writel(reg, PORT_REGS(port) + UTMI_PLL_CTRL_REG); + + /* Impedance Calibration Threshold Setting */ + reg = readl(PORT_REGS(port) + UTMI_CAL_CTRL_REG); + reg &= ~IMPCAL_VTH_MASK; + reg |= IMPCAL_VTH_VAL << IMPCAL_VTH_OFFSET; + writel(reg, PORT_REGS(port) + UTMI_CAL_CTRL_REG); + + /* Set LS TX driver strength coarse control */ + reg = readl(PORT_REGS(port) + UTMI_TX_CH_CTRL_REG); + reg &= ~TX_AMP_MASK; + reg |= TX_AMP_VAL << TX_AMP_OFFSET; + writel(reg, PORT_REGS(port) + UTMI_TX_CH_CTRL_REG); + + /* Disable SQ and enable analog squelch detect */ + reg = readl(PORT_REGS(port) + UTMI_RX_CH_CTRL0_REG); + reg &= ~SQ_DET_EN; + reg |= SQ_ANA_DTC_SEL; + writel(reg, PORT_REGS(port) + UTMI_RX_CH_CTRL0_REG); + + /* + * Set External squelch calibration number and + * enable the External squelch calibration + */ + reg = readl(PORT_REGS(port) + UTMI_RX_CH_CTRL1_REG); + reg &= ~SQ_AMP_CAL_MASK; + reg |= (SQ_AMP_CAL_VAL << SQ_AMP_CAL_OFFSET) | SQ_AMP_CAL_EN; + writel(reg, PORT_REGS(port) + UTMI_RX_CH_CTRL1_REG); + + /* + * Set Control VDAT Reference Voltage - 0.325V and + * Control VSRC Reference Voltage - 0.6V + */ + reg = readl(PORT_REGS(port) + UTMI_CHGDTC_CTRL_REG); + reg &= ~(VDAT_MASK | VSRC_MASK); + reg |= (VDAT_VAL << VDAT_OFFSET) | (VSRC_VAL << VSRC_OFFSET); + writel(reg, PORT_REGS(port) + UTMI_CHGDTC_CTRL_REG); +} + +static int mvebu_cp110_utmi_phy_power_off(struct phy *phy) +{ + struct mvebu_cp110_utmi_port *port = phy_get_drvdata(phy); + struct mvebu_cp110_utmi *utmi = port->priv; + int i; + + /* Power down UTMI PHY port */ + regmap_clear_bits(utmi->syscon, SYSCON_UTMI_CFG_REG(port->id), + UTMI_PHY_CFG_PU_MASK); + + for (i = 0; i < UTMI_PHY_PORTS; i++) { + int test = regmap_test_bits(utmi->syscon, + SYSCON_UTMI_CFG_REG(i), + UTMI_PHY_CFG_PU_MASK); + /* skip PLL shutdown if there are active UTMI PHY ports */ + if (test != 0) + return 0; + } + + /* PLL Power down if all UTMI PHYs are down */ + regmap_clear_bits(utmi->syscon, SYSCON_USB_CFG_REG, USB_CFG_PLL_MASK); + + return 0; +} + +static int mvebu_cp110_utmi_phy_power_on(struct phy *phy) +{ + struct mvebu_cp110_utmi_port *port = phy_get_drvdata(phy); + struct mvebu_cp110_utmi *utmi = port->priv; + struct device *dev = &phy->dev; + int ret; + u32 reg; + + /* It is necessary to power off UTMI before configuration */ + ret = mvebu_cp110_utmi_phy_power_off(phy); + if (ret) { + dev_err(dev, "UTMI power OFF before power ON failed\n"); + return ret; + } + + /* + * If UTMI port is connected to USB Device controller, + * configure the USB MUX prior to UTMI PHY initialization. + * The single USB device controller can be connected + * to UTMI0 or to UTMI1 PHY port, but not to both. + */ + if (port->dr_mode == USB_DR_MODE_PERIPHERAL) { + regmap_update_bits(utmi->syscon, SYSCON_USB_CFG_REG, + USB_CFG_DEVICE_EN_MASK | USB_CFG_DEVICE_MUX_MASK, + USB_CFG_DEVICE_EN_MASK | + (port->id << USB_CFG_DEVICE_MUX_OFFSET)); + } + + /* Set Test suspendm mode and enable Test UTMI select */ + reg = readl(PORT_REGS(port) + UTMI_CTRL_STATUS0_REG); + reg |= SUSPENDM | TEST_SEL; + writel(reg, PORT_REGS(port) + UTMI_CTRL_STATUS0_REG); + + /* Wait for UTMI power down */ + mdelay(1); + + /* PHY port setup first */ + mvebu_cp110_utmi_port_setup(port); + + /* Power UP UTMI PHY */ + regmap_set_bits(utmi->syscon, SYSCON_UTMI_CFG_REG(port->id), + UTMI_PHY_CFG_PU_MASK); + + /* Disable Test UTMI select */ + reg = readl(PORT_REGS(port) + UTMI_CTRL_STATUS0_REG); + reg &= ~TEST_SEL; + writel(reg, PORT_REGS(port) + UTMI_CTRL_STATUS0_REG); + + /* Wait for impedance calibration */ + ret = readl_poll_timeout(PORT_REGS(port) + UTMI_CAL_CTRL_REG, reg, + reg & IMPCAL_DONE, + PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); + if (ret) { + dev_err(dev, "Failed to end UTMI impedance calibration\n"); + return ret; + } + + /* Wait for PLL calibration */ + ret = readl_poll_timeout(PORT_REGS(port) + UTMI_CAL_CTRL_REG, reg, + reg & PLLCAL_DONE, + PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); + if (ret) { + dev_err(dev, "Failed to end UTMI PLL calibration\n"); + return ret; + } + + /* Wait for PLL ready */ + ret = readl_poll_timeout(PORT_REGS(port) + UTMI_PLL_CTRL_REG, reg, + reg & PLL_RDY, + PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); + if (ret) { + dev_err(dev, "PLL is not ready\n"); + return ret; + } + + /* PLL Power up */ + regmap_set_bits(utmi->syscon, SYSCON_USB_CFG_REG, USB_CFG_PLL_MASK); + + return 0; +} + +static const struct phy_ops mvebu_cp110_utmi_phy_ops = { + .power_on = mvebu_cp110_utmi_phy_power_on, + .power_off = mvebu_cp110_utmi_phy_power_off, + .owner = THIS_MODULE, +}; + +static const struct of_device_id mvebu_cp110_utmi_of_match[] = { + { .compatible = "marvell,cp110-utmi-phy" }, + {}, +}; +MODULE_DEVICE_TABLE(of, mvebu_cp110_utmi_of_match); + +static int mvebu_cp110_utmi_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mvebu_cp110_utmi *utmi; + struct phy_provider *provider; + struct device_node *child; + u32 usb_devices = 0; + + utmi = devm_kzalloc(dev, sizeof(*utmi), GFP_KERNEL); + if (!utmi) + return -ENOMEM; + + utmi->dev = dev; + + /* Get system controller region */ + utmi->syscon = syscon_regmap_lookup_by_phandle(dev->of_node, + "marvell,system-controller"); + if (IS_ERR(utmi->syscon)) { + dev_err(dev, "Missing UTMI system controller\n"); + return PTR_ERR(utmi->syscon); + } + + /* Get UTMI memory region */ + utmi->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(utmi->regs)) + return PTR_ERR(utmi->regs); + + for_each_available_child_of_node(dev->of_node, child) { + struct mvebu_cp110_utmi_port *port; + struct phy *phy; + int ret; + u32 port_id; + + ret = of_property_read_u32(child, "reg", &port_id); + if ((ret < 0) || (port_id >= UTMI_PHY_PORTS)) { + dev_err(dev, + "invalid 'reg' property on child %pOF\n", + child); + continue; + } + + port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); + if (!port) { + of_node_put(child); + return -ENOMEM; + } + + port->dr_mode = of_usb_get_dr_mode_by_phy(child, -1); + if ((port->dr_mode != USB_DR_MODE_HOST) && + (port->dr_mode != USB_DR_MODE_PERIPHERAL)) { + dev_err(&pdev->dev, + "Missing dual role setting of the port%d, will use HOST mode\n", + port_id); + port->dr_mode = USB_DR_MODE_HOST; + } + + if (port->dr_mode == USB_DR_MODE_PERIPHERAL) { + usb_devices++; + if (usb_devices > 1) { + dev_err(dev, + "Single USB device allowed! Port%d will use HOST mode\n", + port_id); + port->dr_mode = USB_DR_MODE_HOST; + } + } + + /* Retrieve PHY capabilities */ + utmi->ops = &mvebu_cp110_utmi_phy_ops; + + /* Instantiate the PHY */ + phy = devm_phy_create(dev, child, utmi->ops); + if (IS_ERR(phy)) { + dev_err(dev, "Failed to create the UTMI PHY\n"); + of_node_put(child); + return PTR_ERR(phy); + } + + port->priv = utmi; + port->id = port_id; + phy_set_drvdata(phy, port); + + /* Ensure the PHY is powered off */ + mvebu_cp110_utmi_phy_power_off(phy); + } + + dev_set_drvdata(dev, utmi); + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(provider); +} + +static struct platform_driver mvebu_cp110_utmi_driver = { + .probe = mvebu_cp110_utmi_phy_probe, + .driver = { + .name = "mvebu-cp110-utmi-phy", + .of_match_table = mvebu_cp110_utmi_of_match, + }, +}; +module_platform_driver(mvebu_cp110_utmi_driver); + +MODULE_AUTHOR("Konstatin Porotchkin <kostap@marvell.com>"); +MODULE_DESCRIPTION("Marvell Armada CP110 UTMI PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/marvell/phy-mvebu-sata.c b/drivers/phy/marvell/phy-mvebu-sata.c new file mode 100644 index 000000000..51a4646e2 --- /dev/null +++ b/drivers/phy/marvell/phy-mvebu-sata.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * phy-mvebu-sata.c: SATA Phy driver for the Marvell mvebu SoCs. + * + * Copyright (C) 2013 Andrew Lunn <andrew@lunn.ch> + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/phy/phy.h> +#include <linux/io.h> +#include <linux/platform_device.h> + +struct priv { + struct clk *clk; + void __iomem *base; +}; + +#define SATA_PHY_MODE_2 0x0330 +#define MODE_2_FORCE_PU_TX BIT(0) +#define MODE_2_FORCE_PU_RX BIT(1) +#define MODE_2_PU_PLL BIT(2) +#define MODE_2_PU_IVREF BIT(3) +#define SATA_IF_CTRL 0x0050 +#define CTRL_PHY_SHUTDOWN BIT(9) + +static int phy_mvebu_sata_power_on(struct phy *phy) +{ + struct priv *priv = phy_get_drvdata(phy); + u32 reg; + + clk_prepare_enable(priv->clk); + + /* Enable PLL and IVREF */ + reg = readl(priv->base + SATA_PHY_MODE_2); + reg |= (MODE_2_FORCE_PU_TX | MODE_2_FORCE_PU_RX | + MODE_2_PU_PLL | MODE_2_PU_IVREF); + writel(reg , priv->base + SATA_PHY_MODE_2); + + /* Enable PHY */ + reg = readl(priv->base + SATA_IF_CTRL); + reg &= ~CTRL_PHY_SHUTDOWN; + writel(reg, priv->base + SATA_IF_CTRL); + + clk_disable_unprepare(priv->clk); + + return 0; +} + +static int phy_mvebu_sata_power_off(struct phy *phy) +{ + struct priv *priv = phy_get_drvdata(phy); + u32 reg; + + clk_prepare_enable(priv->clk); + + /* Disable PLL and IVREF */ + reg = readl(priv->base + SATA_PHY_MODE_2); + reg &= ~(MODE_2_FORCE_PU_TX | MODE_2_FORCE_PU_RX | + MODE_2_PU_PLL | MODE_2_PU_IVREF); + writel(reg, priv->base + SATA_PHY_MODE_2); + + /* Disable PHY */ + reg = readl(priv->base + SATA_IF_CTRL); + reg |= CTRL_PHY_SHUTDOWN; + writel(reg, priv->base + SATA_IF_CTRL); + + clk_disable_unprepare(priv->clk); + + return 0; +} + +static const struct phy_ops phy_mvebu_sata_ops = { + .power_on = phy_mvebu_sata_power_on, + .power_off = phy_mvebu_sata_power_off, + .owner = THIS_MODULE, +}; + +static int phy_mvebu_sata_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct priv *priv; + struct phy *phy; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->clk = devm_clk_get(&pdev->dev, "sata"); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + phy = devm_phy_create(&pdev->dev, NULL, &phy_mvebu_sata_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + phy_set_drvdata(phy, priv); + + phy_provider = devm_of_phy_provider_register(&pdev->dev, + of_phy_simple_xlate); + if (IS_ERR(phy_provider)) + return PTR_ERR(phy_provider); + + /* The boot loader may of left it on. Turn it off. */ + phy_mvebu_sata_power_off(phy); + + return 0; +} + +static const struct of_device_id phy_mvebu_sata_of_match[] = { + { .compatible = "marvell,mvebu-sata-phy" }, + { }, +}; + +static struct platform_driver phy_mvebu_sata_driver = { + .probe = phy_mvebu_sata_probe, + .driver = { + .name = "phy-mvebu-sata", + .of_match_table = phy_mvebu_sata_of_match, + } +}; +builtin_platform_driver(phy_mvebu_sata_driver); diff --git a/drivers/phy/marvell/phy-pxa-28nm-hsic.c b/drivers/phy/marvell/phy-pxa-28nm-hsic.c new file mode 100644 index 000000000..c5c100563 --- /dev/null +++ b/drivers/phy/marvell/phy-pxa-28nm-hsic.c @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2015 Linaro, Ltd. + * Rob Herring <robh@kernel.org> + * + * Based on vendor driver: + * Copyright (C) 2013 Marvell Inc. + * Author: Chao Xie <xiechao.mail@gmail.com> + */ + +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/phy/phy.h> + +#define PHY_28NM_HSIC_CTRL 0x08 +#define PHY_28NM_HSIC_IMPCAL_CAL 0x18 +#define PHY_28NM_HSIC_PLL_CTRL01 0x1c +#define PHY_28NM_HSIC_PLL_CTRL2 0x20 +#define PHY_28NM_HSIC_INT 0x28 + +#define PHY_28NM_HSIC_PLL_SELLPFR_SHIFT 26 +#define PHY_28NM_HSIC_PLL_FBDIV_SHIFT 0 +#define PHY_28NM_HSIC_PLL_REFDIV_SHIFT 9 + +#define PHY_28NM_HSIC_S2H_PU_PLL BIT(10) +#define PHY_28NM_HSIC_H2S_PLL_LOCK BIT(15) +#define PHY_28NM_HSIC_S2H_HSIC_EN BIT(7) +#define S2H_DRV_SE0_4RESUME BIT(14) +#define PHY_28NM_HSIC_H2S_IMPCAL_DONE BIT(27) + +#define PHY_28NM_HSIC_CONNECT_INT BIT(1) +#define PHY_28NM_HSIC_HS_READY_INT BIT(2) + +struct mv_hsic_phy { + struct phy *phy; + struct platform_device *pdev; + void __iomem *base; + struct clk *clk; +}; + +static int wait_for_reg(void __iomem *reg, u32 mask, u32 ms) +{ + u32 val; + + return readl_poll_timeout(reg, val, ((val & mask) == mask), + 1000, 1000 * ms); +} + +static int mv_hsic_phy_init(struct phy *phy) +{ + struct mv_hsic_phy *mv_phy = phy_get_drvdata(phy); + struct platform_device *pdev = mv_phy->pdev; + void __iomem *base = mv_phy->base; + int ret; + + clk_prepare_enable(mv_phy->clk); + + /* Set reference clock */ + writel(0x1 << PHY_28NM_HSIC_PLL_SELLPFR_SHIFT | + 0xf0 << PHY_28NM_HSIC_PLL_FBDIV_SHIFT | + 0xd << PHY_28NM_HSIC_PLL_REFDIV_SHIFT, + base + PHY_28NM_HSIC_PLL_CTRL01); + + /* Turn on PLL */ + writel(readl(base + PHY_28NM_HSIC_PLL_CTRL2) | + PHY_28NM_HSIC_S2H_PU_PLL, + base + PHY_28NM_HSIC_PLL_CTRL2); + + /* Make sure PHY PLL is locked */ + ret = wait_for_reg(base + PHY_28NM_HSIC_PLL_CTRL2, + PHY_28NM_HSIC_H2S_PLL_LOCK, 100); + if (ret) { + dev_err(&pdev->dev, "HSIC PHY PLL not locked after 100mS."); + clk_disable_unprepare(mv_phy->clk); + } + + return ret; +} + +static int mv_hsic_phy_power_on(struct phy *phy) +{ + struct mv_hsic_phy *mv_phy = phy_get_drvdata(phy); + struct platform_device *pdev = mv_phy->pdev; + void __iomem *base = mv_phy->base; + u32 reg; + int ret; + + reg = readl(base + PHY_28NM_HSIC_CTRL); + /* Avoid SE0 state when resume for some device will take it as reset */ + reg &= ~S2H_DRV_SE0_4RESUME; + reg |= PHY_28NM_HSIC_S2H_HSIC_EN; /* Enable HSIC PHY */ + writel(reg, base + PHY_28NM_HSIC_CTRL); + + /* + * Calibration Timing + * ____________________________ + * CAL START ___| + * ____________________ + * CAL_DONE ___________| + * | 400us | + */ + + /* Make sure PHY Calibration is ready */ + ret = wait_for_reg(base + PHY_28NM_HSIC_IMPCAL_CAL, + PHY_28NM_HSIC_H2S_IMPCAL_DONE, 100); + if (ret) { + dev_warn(&pdev->dev, "HSIC PHY READY not set after 100mS."); + return ret; + } + + /* Waiting for HSIC connect int*/ + ret = wait_for_reg(base + PHY_28NM_HSIC_INT, + PHY_28NM_HSIC_CONNECT_INT, 200); + if (ret) + dev_warn(&pdev->dev, "HSIC wait for connect interrupt timeout."); + + return ret; +} + +static int mv_hsic_phy_power_off(struct phy *phy) +{ + struct mv_hsic_phy *mv_phy = phy_get_drvdata(phy); + void __iomem *base = mv_phy->base; + + writel(readl(base + PHY_28NM_HSIC_CTRL) & ~PHY_28NM_HSIC_S2H_HSIC_EN, + base + PHY_28NM_HSIC_CTRL); + + return 0; +} + +static int mv_hsic_phy_exit(struct phy *phy) +{ + struct mv_hsic_phy *mv_phy = phy_get_drvdata(phy); + void __iomem *base = mv_phy->base; + + /* Turn off PLL */ + writel(readl(base + PHY_28NM_HSIC_PLL_CTRL2) & + ~PHY_28NM_HSIC_S2H_PU_PLL, + base + PHY_28NM_HSIC_PLL_CTRL2); + + clk_disable_unprepare(mv_phy->clk); + return 0; +} + + +static const struct phy_ops hsic_ops = { + .init = mv_hsic_phy_init, + .power_on = mv_hsic_phy_power_on, + .power_off = mv_hsic_phy_power_off, + .exit = mv_hsic_phy_exit, + .owner = THIS_MODULE, +}; + +static int mv_hsic_phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct mv_hsic_phy *mv_phy; + + mv_phy = devm_kzalloc(&pdev->dev, sizeof(*mv_phy), GFP_KERNEL); + if (!mv_phy) + return -ENOMEM; + + mv_phy->pdev = pdev; + + mv_phy->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(mv_phy->clk)) { + dev_err(&pdev->dev, "failed to get clock.\n"); + return PTR_ERR(mv_phy->clk); + } + + mv_phy->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mv_phy->base)) + return PTR_ERR(mv_phy->base); + + mv_phy->phy = devm_phy_create(&pdev->dev, pdev->dev.of_node, &hsic_ops); + if (IS_ERR(mv_phy->phy)) + return PTR_ERR(mv_phy->phy); + + phy_set_drvdata(mv_phy->phy, mv_phy); + + phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id mv_hsic_phy_dt_match[] = { + { .compatible = "marvell,pxa1928-hsic-phy", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mv_hsic_phy_dt_match); + +static struct platform_driver mv_hsic_phy_driver = { + .probe = mv_hsic_phy_probe, + .driver = { + .name = "mv-hsic-phy", + .of_match_table = of_match_ptr(mv_hsic_phy_dt_match), + }, +}; +module_platform_driver(mv_hsic_phy_driver); + +MODULE_AUTHOR("Rob Herring <robh@kernel.org>"); +MODULE_DESCRIPTION("Marvell HSIC phy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/marvell/phy-pxa-28nm-usb2.c b/drivers/phy/marvell/phy-pxa-28nm-usb2.c new file mode 100644 index 000000000..0b390b9d2 --- /dev/null +++ b/drivers/phy/marvell/phy-pxa-28nm-usb2.c @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2015 Linaro, Ltd. + * Rob Herring <robh@kernel.org> + * + * Based on vendor driver: + * Copyright (C) 2013 Marvell Inc. + * Author: Chao Xie <xiechao.mail@gmail.com> + */ + +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/phy/phy.h> + +/* USB PXA1928 PHY mapping */ +#define PHY_28NM_PLL_REG0 0x0 +#define PHY_28NM_PLL_REG1 0x4 +#define PHY_28NM_CAL_REG 0x8 +#define PHY_28NM_TX_REG0 0x0c +#define PHY_28NM_TX_REG1 0x10 +#define PHY_28NM_RX_REG0 0x14 +#define PHY_28NM_RX_REG1 0x18 +#define PHY_28NM_DIG_REG0 0x1c +#define PHY_28NM_DIG_REG1 0x20 +#define PHY_28NM_TEST_REG0 0x24 +#define PHY_28NM_TEST_REG1 0x28 +#define PHY_28NM_MOC_REG 0x2c +#define PHY_28NM_PHY_RESERVE 0x30 +#define PHY_28NM_OTG_REG 0x34 +#define PHY_28NM_CHRG_DET 0x38 +#define PHY_28NM_CTRL_REG0 0xc4 +#define PHY_28NM_CTRL_REG1 0xc8 +#define PHY_28NM_CTRL_REG2 0xd4 +#define PHY_28NM_CTRL_REG3 0xdc + +/* PHY_28NM_PLL_REG0 */ +#define PHY_28NM_PLL_READY BIT(31) + +#define PHY_28NM_PLL_SELLPFR_SHIFT 28 +#define PHY_28NM_PLL_SELLPFR_MASK (0x3 << 28) + +#define PHY_28NM_PLL_FBDIV_SHIFT 16 +#define PHY_28NM_PLL_FBDIV_MASK (0x1ff << 16) + +#define PHY_28NM_PLL_ICP_SHIFT 8 +#define PHY_28NM_PLL_ICP_MASK (0x7 << 8) + +#define PHY_28NM_PLL_REFDIV_SHIFT 0 +#define PHY_28NM_PLL_REFDIV_MASK 0x7f + +/* PHY_28NM_PLL_REG1 */ +#define PHY_28NM_PLL_PU_BY_REG BIT(1) + +#define PHY_28NM_PLL_PU_PLL BIT(0) + +/* PHY_28NM_CAL_REG */ +#define PHY_28NM_PLL_PLLCAL_DONE BIT(31) + +#define PHY_28NM_PLL_IMPCAL_DONE BIT(23) + +#define PHY_28NM_PLL_KVCO_SHIFT 16 +#define PHY_28NM_PLL_KVCO_MASK (0x7 << 16) + +#define PHY_28NM_PLL_CAL12_SHIFT 20 +#define PHY_28NM_PLL_CAL12_MASK (0x3 << 20) + +#define PHY_28NM_IMPCAL_VTH_SHIFT 8 +#define PHY_28NM_IMPCAL_VTH_MASK (0x7 << 8) + +#define PHY_28NM_PLLCAL_START_SHIFT 22 +#define PHY_28NM_IMPCAL_START_SHIFT 13 + +/* PHY_28NM_TX_REG0 */ +#define PHY_28NM_TX_PU_BY_REG BIT(25) + +#define PHY_28NM_TX_PU_ANA BIT(24) + +#define PHY_28NM_TX_AMP_SHIFT 20 +#define PHY_28NM_TX_AMP_MASK (0x7 << 20) + +/* PHY_28NM_RX_REG0 */ +#define PHY_28NM_RX_SQ_THRESH_SHIFT 0 +#define PHY_28NM_RX_SQ_THRESH_MASK (0xf << 0) + +/* PHY_28NM_RX_REG1 */ +#define PHY_28NM_RX_SQCAL_DONE BIT(31) + +/* PHY_28NM_DIG_REG0 */ +#define PHY_28NM_DIG_BITSTAFFING_ERR BIT(31) +#define PHY_28NM_DIG_SYNC_ERR BIT(30) + +#define PHY_28NM_DIG_SQ_FILT_SHIFT 16 +#define PHY_28NM_DIG_SQ_FILT_MASK (0x7 << 16) + +#define PHY_28NM_DIG_SQ_BLK_SHIFT 12 +#define PHY_28NM_DIG_SQ_BLK_MASK (0x7 << 12) + +#define PHY_28NM_DIG_SYNC_NUM_SHIFT 0 +#define PHY_28NM_DIG_SYNC_NUM_MASK (0x3 << 0) + +#define PHY_28NM_PLL_LOCK_BYPASS BIT(7) + +/* PHY_28NM_OTG_REG */ +#define PHY_28NM_OTG_CONTROL_BY_PIN BIT(5) +#define PHY_28NM_OTG_PU_OTG BIT(4) + +#define PHY_28NM_CHGDTC_ENABLE_SWITCH_DM_SHIFT_28 13 +#define PHY_28NM_CHGDTC_ENABLE_SWITCH_DP_SHIFT_28 12 +#define PHY_28NM_CHGDTC_VSRC_CHARGE_SHIFT_28 10 +#define PHY_28NM_CHGDTC_VDAT_CHARGE_SHIFT_28 8 +#define PHY_28NM_CHGDTC_CDP_DM_AUTO_SWITCH_SHIFT_28 7 +#define PHY_28NM_CHGDTC_DP_DM_SWAP_SHIFT_28 6 +#define PHY_28NM_CHGDTC_PU_CHRG_DTC_SHIFT_28 5 +#define PHY_28NM_CHGDTC_PD_EN_SHIFT_28 4 +#define PHY_28NM_CHGDTC_DCP_EN_SHIFT_28 3 +#define PHY_28NM_CHGDTC_CDP_EN_SHIFT_28 2 +#define PHY_28NM_CHGDTC_TESTMON_CHRGDTC_SHIFT_28 0 + +#define PHY_28NM_CTRL1_CHRG_DTC_OUT_SHIFT_28 4 +#define PHY_28NM_CTRL1_VBUSDTC_OUT_SHIFT_28 2 + +#define PHY_28NM_CTRL3_OVERWRITE BIT(0) +#define PHY_28NM_CTRL3_VBUS_VALID BIT(4) +#define PHY_28NM_CTRL3_AVALID BIT(5) +#define PHY_28NM_CTRL3_BVALID BIT(6) + +struct mv_usb2_phy { + struct phy *phy; + struct platform_device *pdev; + void __iomem *base; + struct clk *clk; +}; + +static int wait_for_reg(void __iomem *reg, u32 mask, u32 ms) +{ + u32 val; + + return readl_poll_timeout(reg, val, ((val & mask) == mask), + 1000, 1000 * ms); +} + +static int mv_usb2_phy_28nm_init(struct phy *phy) +{ + struct mv_usb2_phy *mv_phy = phy_get_drvdata(phy); + struct platform_device *pdev = mv_phy->pdev; + void __iomem *base = mv_phy->base; + u32 reg; + int ret; + + clk_prepare_enable(mv_phy->clk); + + /* PHY_28NM_PLL_REG0 */ + reg = readl(base + PHY_28NM_PLL_REG0) & + ~(PHY_28NM_PLL_SELLPFR_MASK | PHY_28NM_PLL_FBDIV_MASK + | PHY_28NM_PLL_ICP_MASK | PHY_28NM_PLL_REFDIV_MASK); + writel(reg | (0x1 << PHY_28NM_PLL_SELLPFR_SHIFT + | 0xf0 << PHY_28NM_PLL_FBDIV_SHIFT + | 0x3 << PHY_28NM_PLL_ICP_SHIFT + | 0xd << PHY_28NM_PLL_REFDIV_SHIFT), + base + PHY_28NM_PLL_REG0); + + /* PHY_28NM_PLL_REG1 */ + reg = readl(base + PHY_28NM_PLL_REG1); + writel(reg | PHY_28NM_PLL_PU_PLL | PHY_28NM_PLL_PU_BY_REG, + base + PHY_28NM_PLL_REG1); + + /* PHY_28NM_TX_REG0 */ + reg = readl(base + PHY_28NM_TX_REG0) & ~PHY_28NM_TX_AMP_MASK; + writel(reg | PHY_28NM_TX_PU_BY_REG | 0x3 << PHY_28NM_TX_AMP_SHIFT | + PHY_28NM_TX_PU_ANA, + base + PHY_28NM_TX_REG0); + + /* PHY_28NM_RX_REG0 */ + reg = readl(base + PHY_28NM_RX_REG0) & ~PHY_28NM_RX_SQ_THRESH_MASK; + writel(reg | 0xa << PHY_28NM_RX_SQ_THRESH_SHIFT, + base + PHY_28NM_RX_REG0); + + /* PHY_28NM_DIG_REG0 */ + reg = readl(base + PHY_28NM_DIG_REG0) & + ~(PHY_28NM_DIG_BITSTAFFING_ERR | PHY_28NM_DIG_SYNC_ERR | + PHY_28NM_DIG_SQ_FILT_MASK | PHY_28NM_DIG_SQ_BLK_MASK | + PHY_28NM_DIG_SYNC_NUM_MASK); + writel(reg | (0x1 << PHY_28NM_DIG_SYNC_NUM_SHIFT | + PHY_28NM_PLL_LOCK_BYPASS), + base + PHY_28NM_DIG_REG0); + + /* PHY_28NM_OTG_REG */ + reg = readl(base + PHY_28NM_OTG_REG) | PHY_28NM_OTG_PU_OTG; + writel(reg & ~PHY_28NM_OTG_CONTROL_BY_PIN, base + PHY_28NM_OTG_REG); + + /* + * Calibration Timing + * ____________________________ + * CAL START ___| + * ____________________ + * CAL_DONE ___________| + * | 400us | + */ + + /* Make sure PHY Calibration is ready */ + ret = wait_for_reg(base + PHY_28NM_CAL_REG, + PHY_28NM_PLL_PLLCAL_DONE | PHY_28NM_PLL_IMPCAL_DONE, + 100); + if (ret) { + dev_warn(&pdev->dev, "USB PHY PLL calibrate not done after 100mS."); + goto err_clk; + } + ret = wait_for_reg(base + PHY_28NM_RX_REG1, + PHY_28NM_RX_SQCAL_DONE, 100); + if (ret) { + dev_warn(&pdev->dev, "USB PHY RX SQ calibrate not done after 100mS."); + goto err_clk; + } + /* Make sure PHY PLL is ready */ + ret = wait_for_reg(base + PHY_28NM_PLL_REG0, PHY_28NM_PLL_READY, 100); + if (ret) { + dev_warn(&pdev->dev, "PLL_READY not set after 100mS."); + goto err_clk; + } + + return 0; +err_clk: + clk_disable_unprepare(mv_phy->clk); + return ret; +} + +static int mv_usb2_phy_28nm_power_on(struct phy *phy) +{ + struct mv_usb2_phy *mv_phy = phy_get_drvdata(phy); + void __iomem *base = mv_phy->base; + + writel(readl(base + PHY_28NM_CTRL_REG3) | + (PHY_28NM_CTRL3_OVERWRITE | PHY_28NM_CTRL3_VBUS_VALID | + PHY_28NM_CTRL3_AVALID | PHY_28NM_CTRL3_BVALID), + base + PHY_28NM_CTRL_REG3); + + return 0; +} + +static int mv_usb2_phy_28nm_power_off(struct phy *phy) +{ + struct mv_usb2_phy *mv_phy = phy_get_drvdata(phy); + void __iomem *base = mv_phy->base; + + writel(readl(base + PHY_28NM_CTRL_REG3) | + ~(PHY_28NM_CTRL3_OVERWRITE | PHY_28NM_CTRL3_VBUS_VALID + | PHY_28NM_CTRL3_AVALID | PHY_28NM_CTRL3_BVALID), + base + PHY_28NM_CTRL_REG3); + + return 0; +} + +static int mv_usb2_phy_28nm_exit(struct phy *phy) +{ + struct mv_usb2_phy *mv_phy = phy_get_drvdata(phy); + void __iomem *base = mv_phy->base; + unsigned int val; + + val = readw(base + PHY_28NM_PLL_REG1); + val &= ~PHY_28NM_PLL_PU_PLL; + writew(val, base + PHY_28NM_PLL_REG1); + + /* power down PHY Analog part */ + val = readw(base + PHY_28NM_TX_REG0); + val &= ~PHY_28NM_TX_PU_ANA; + writew(val, base + PHY_28NM_TX_REG0); + + /* power down PHY OTG part */ + val = readw(base + PHY_28NM_OTG_REG); + val &= ~PHY_28NM_OTG_PU_OTG; + writew(val, base + PHY_28NM_OTG_REG); + + clk_disable_unprepare(mv_phy->clk); + return 0; +} + +static const struct phy_ops usb_ops = { + .init = mv_usb2_phy_28nm_init, + .power_on = mv_usb2_phy_28nm_power_on, + .power_off = mv_usb2_phy_28nm_power_off, + .exit = mv_usb2_phy_28nm_exit, + .owner = THIS_MODULE, +}; + +static int mv_usb2_phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct mv_usb2_phy *mv_phy; + + mv_phy = devm_kzalloc(&pdev->dev, sizeof(*mv_phy), GFP_KERNEL); + if (!mv_phy) + return -ENOMEM; + + mv_phy->pdev = pdev; + + mv_phy->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(mv_phy->clk)) { + dev_err(&pdev->dev, "failed to get clock.\n"); + return PTR_ERR(mv_phy->clk); + } + + mv_phy->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mv_phy->base)) + return PTR_ERR(mv_phy->base); + + mv_phy->phy = devm_phy_create(&pdev->dev, pdev->dev.of_node, &usb_ops); + if (IS_ERR(mv_phy->phy)) + return PTR_ERR(mv_phy->phy); + + phy_set_drvdata(mv_phy->phy, mv_phy); + + phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id mv_usbphy_dt_match[] = { + { .compatible = "marvell,pxa1928-usb-phy", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mv_usbphy_dt_match); + +static struct platform_driver mv_usb2_phy_driver = { + .probe = mv_usb2_phy_probe, + .driver = { + .name = "mv-usb2-phy", + .of_match_table = of_match_ptr(mv_usbphy_dt_match), + }, +}; +module_platform_driver(mv_usb2_phy_driver); + +MODULE_AUTHOR("Rob Herring <robh@kernel.org>"); +MODULE_DESCRIPTION("Marvell USB2 phy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/marvell/phy-pxa-usb.c b/drivers/phy/marvell/phy-pxa-usb.c new file mode 100644 index 000000000..ffe889893 --- /dev/null +++ b/drivers/phy/marvell/phy-pxa-usb.c @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2011 Marvell International Ltd. All rights reserved. + * Copyright (C) 2018 Lubomir Rintel <lkundrak@v3.sk> + */ + +#include <dt-bindings/phy/phy.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> + +/* phy regs */ +#define UTMI_REVISION 0x0 +#define UTMI_CTRL 0x4 +#define UTMI_PLL 0x8 +#define UTMI_TX 0xc +#define UTMI_RX 0x10 +#define UTMI_IVREF 0x14 +#define UTMI_T0 0x18 +#define UTMI_T1 0x1c +#define UTMI_T2 0x20 +#define UTMI_T3 0x24 +#define UTMI_T4 0x28 +#define UTMI_T5 0x2c +#define UTMI_RESERVE 0x30 +#define UTMI_USB_INT 0x34 +#define UTMI_DBG_CTL 0x38 +#define UTMI_OTG_ADDON 0x3c + +/* For UTMICTRL Register */ +#define UTMI_CTRL_USB_CLK_EN (1 << 31) +/* pxa168 */ +#define UTMI_CTRL_SUSPEND_SET1 (1 << 30) +#define UTMI_CTRL_SUSPEND_SET2 (1 << 29) +#define UTMI_CTRL_RXBUF_PDWN (1 << 24) +#define UTMI_CTRL_TXBUF_PDWN (1 << 11) + +#define UTMI_CTRL_INPKT_DELAY_SHIFT 30 +#define UTMI_CTRL_INPKT_DELAY_SOF_SHIFT 28 +#define UTMI_CTRL_PU_REF_SHIFT 20 +#define UTMI_CTRL_ARC_PULLDN_SHIFT 12 +#define UTMI_CTRL_PLL_PWR_UP_SHIFT 1 +#define UTMI_CTRL_PWR_UP_SHIFT 0 + +/* For UTMI_PLL Register */ +#define UTMI_PLL_PLLCALI12_SHIFT 29 +#define UTMI_PLL_PLLCALI12_MASK (0x3 << 29) + +#define UTMI_PLL_PLLVDD18_SHIFT 27 +#define UTMI_PLL_PLLVDD18_MASK (0x3 << 27) + +#define UTMI_PLL_PLLVDD12_SHIFT 25 +#define UTMI_PLL_PLLVDD12_MASK (0x3 << 25) + +#define UTMI_PLL_CLK_BLK_EN_SHIFT 24 +#define CLK_BLK_EN (0x1 << 24) +#define PLL_READY (0x1 << 23) +#define KVCO_EXT (0x1 << 22) +#define VCOCAL_START (0x1 << 21) + +#define UTMI_PLL_KVCO_SHIFT 15 +#define UTMI_PLL_KVCO_MASK (0x7 << 15) + +#define UTMI_PLL_ICP_SHIFT 12 +#define UTMI_PLL_ICP_MASK (0x7 << 12) + +#define UTMI_PLL_FBDIV_SHIFT 4 +#define UTMI_PLL_FBDIV_MASK (0xFF << 4) + +#define UTMI_PLL_REFDIV_SHIFT 0 +#define UTMI_PLL_REFDIV_MASK (0xF << 0) + +/* For UTMI_TX Register */ +#define UTMI_TX_REG_EXT_FS_RCAL_SHIFT 27 +#define UTMI_TX_REG_EXT_FS_RCAL_MASK (0xf << 27) + +#define UTMI_TX_REG_EXT_FS_RCAL_EN_SHIFT 26 +#define UTMI_TX_REG_EXT_FS_RCAL_EN_MASK (0x1 << 26) + +#define UTMI_TX_TXVDD12_SHIFT 22 +#define UTMI_TX_TXVDD12_MASK (0x3 << 22) + +#define UTMI_TX_CK60_PHSEL_SHIFT 17 +#define UTMI_TX_CK60_PHSEL_MASK (0xf << 17) + +#define UTMI_TX_IMPCAL_VTH_SHIFT 14 +#define UTMI_TX_IMPCAL_VTH_MASK (0x7 << 14) + +#define REG_RCAL_START (0x1 << 12) + +#define UTMI_TX_LOW_VDD_EN_SHIFT 11 + +#define UTMI_TX_AMP_SHIFT 0 +#define UTMI_TX_AMP_MASK (0x7 << 0) + +/* For UTMI_RX Register */ +#define UTMI_REG_SQ_LENGTH_SHIFT 15 +#define UTMI_REG_SQ_LENGTH_MASK (0x3 << 15) + +#define UTMI_RX_SQ_THRESH_SHIFT 4 +#define UTMI_RX_SQ_THRESH_MASK (0xf << 4) + +#define UTMI_OTG_ADDON_OTG_ON (1 << 0) + +enum pxa_usb_phy_version { + PXA_USB_PHY_MMP2, + PXA_USB_PHY_PXA910, + PXA_USB_PHY_PXA168, +}; + +struct pxa_usb_phy { + struct phy *phy; + void __iomem *base; + enum pxa_usb_phy_version version; +}; + +/***************************************************************************** + * The registers read/write routines + *****************************************************************************/ + +static unsigned int u2o_get(void __iomem *base, unsigned int offset) +{ + return readl_relaxed(base + offset); +} + +static void u2o_set(void __iomem *base, unsigned int offset, + unsigned int value) +{ + u32 reg; + + reg = readl_relaxed(base + offset); + reg |= value; + writel_relaxed(reg, base + offset); + readl_relaxed(base + offset); +} + +static void u2o_clear(void __iomem *base, unsigned int offset, + unsigned int value) +{ + u32 reg; + + reg = readl_relaxed(base + offset); + reg &= ~value; + writel_relaxed(reg, base + offset); + readl_relaxed(base + offset); +} + +static void u2o_write(void __iomem *base, unsigned int offset, + unsigned int value) +{ + writel_relaxed(value, base + offset); + readl_relaxed(base + offset); +} + +static int pxa_usb_phy_init(struct phy *phy) +{ + struct pxa_usb_phy *pxa_usb_phy = phy_get_drvdata(phy); + void __iomem *base = pxa_usb_phy->base; + int loops; + + dev_info(&phy->dev, "initializing Marvell PXA USB PHY"); + + /* Initialize the USB PHY power */ + if (pxa_usb_phy->version == PXA_USB_PHY_PXA910) { + u2o_set(base, UTMI_CTRL, (1<<UTMI_CTRL_INPKT_DELAY_SOF_SHIFT) + | (1<<UTMI_CTRL_PU_REF_SHIFT)); + } + + u2o_set(base, UTMI_CTRL, 1<<UTMI_CTRL_PLL_PWR_UP_SHIFT); + u2o_set(base, UTMI_CTRL, 1<<UTMI_CTRL_PWR_UP_SHIFT); + + /* UTMI_PLL settings */ + u2o_clear(base, UTMI_PLL, UTMI_PLL_PLLVDD18_MASK + | UTMI_PLL_PLLVDD12_MASK | UTMI_PLL_PLLCALI12_MASK + | UTMI_PLL_FBDIV_MASK | UTMI_PLL_REFDIV_MASK + | UTMI_PLL_ICP_MASK | UTMI_PLL_KVCO_MASK); + + u2o_set(base, UTMI_PLL, 0xee<<UTMI_PLL_FBDIV_SHIFT + | 0xb<<UTMI_PLL_REFDIV_SHIFT | 3<<UTMI_PLL_PLLVDD18_SHIFT + | 3<<UTMI_PLL_PLLVDD12_SHIFT | 3<<UTMI_PLL_PLLCALI12_SHIFT + | 1<<UTMI_PLL_ICP_SHIFT | 3<<UTMI_PLL_KVCO_SHIFT); + + /* UTMI_TX */ + u2o_clear(base, UTMI_TX, UTMI_TX_REG_EXT_FS_RCAL_EN_MASK + | UTMI_TX_TXVDD12_MASK | UTMI_TX_CK60_PHSEL_MASK + | UTMI_TX_IMPCAL_VTH_MASK | UTMI_TX_REG_EXT_FS_RCAL_MASK + | UTMI_TX_AMP_MASK); + u2o_set(base, UTMI_TX, 3<<UTMI_TX_TXVDD12_SHIFT + | 4<<UTMI_TX_CK60_PHSEL_SHIFT | 4<<UTMI_TX_IMPCAL_VTH_SHIFT + | 8<<UTMI_TX_REG_EXT_FS_RCAL_SHIFT | 3<<UTMI_TX_AMP_SHIFT); + + /* UTMI_RX */ + u2o_clear(base, UTMI_RX, UTMI_RX_SQ_THRESH_MASK + | UTMI_REG_SQ_LENGTH_MASK); + u2o_set(base, UTMI_RX, 7<<UTMI_RX_SQ_THRESH_SHIFT + | 2<<UTMI_REG_SQ_LENGTH_SHIFT); + + /* UTMI_IVREF */ + if (pxa_usb_phy->version == PXA_USB_PHY_PXA168) { + /* + * fixing Microsoft Altair board interface with NEC hub issue - + * Set UTMI_IVREF from 0x4a3 to 0x4bf + */ + u2o_write(base, UTMI_IVREF, 0x4bf); + } + + /* toggle VCOCAL_START bit of UTMI_PLL */ + udelay(200); + u2o_set(base, UTMI_PLL, VCOCAL_START); + udelay(40); + u2o_clear(base, UTMI_PLL, VCOCAL_START); + + /* toggle REG_RCAL_START bit of UTMI_TX */ + udelay(400); + u2o_set(base, UTMI_TX, REG_RCAL_START); + udelay(40); + u2o_clear(base, UTMI_TX, REG_RCAL_START); + udelay(400); + + /* Make sure PHY PLL is ready */ + loops = 0; + while ((u2o_get(base, UTMI_PLL) & PLL_READY) == 0) { + mdelay(1); + loops++; + if (loops > 100) { + dev_warn(&phy->dev, "calibrate timeout, UTMI_PLL %x\n", + u2o_get(base, UTMI_PLL)); + break; + } + } + + if (pxa_usb_phy->version == PXA_USB_PHY_PXA168) { + u2o_set(base, UTMI_RESERVE, 1 << 5); + /* Turn on UTMI PHY OTG extension */ + u2o_write(base, UTMI_OTG_ADDON, 1); + } + + return 0; + +} + +static int pxa_usb_phy_exit(struct phy *phy) +{ + struct pxa_usb_phy *pxa_usb_phy = phy_get_drvdata(phy); + void __iomem *base = pxa_usb_phy->base; + + dev_info(&phy->dev, "deinitializing Marvell PXA USB PHY"); + + if (pxa_usb_phy->version == PXA_USB_PHY_PXA168) + u2o_clear(base, UTMI_OTG_ADDON, UTMI_OTG_ADDON_OTG_ON); + + u2o_clear(base, UTMI_CTRL, UTMI_CTRL_RXBUF_PDWN); + u2o_clear(base, UTMI_CTRL, UTMI_CTRL_TXBUF_PDWN); + u2o_clear(base, UTMI_CTRL, UTMI_CTRL_USB_CLK_EN); + u2o_clear(base, UTMI_CTRL, 1<<UTMI_CTRL_PWR_UP_SHIFT); + u2o_clear(base, UTMI_CTRL, 1<<UTMI_CTRL_PLL_PWR_UP_SHIFT); + + return 0; +} + +static const struct phy_ops pxa_usb_phy_ops = { + .init = pxa_usb_phy_init, + .exit = pxa_usb_phy_exit, + .owner = THIS_MODULE, +}; + +static const struct of_device_id pxa_usb_phy_of_match[] = { + { + .compatible = "marvell,mmp2-usb-phy", + .data = (void *)PXA_USB_PHY_MMP2, + }, { + .compatible = "marvell,pxa910-usb-phy", + .data = (void *)PXA_USB_PHY_PXA910, + }, { + .compatible = "marvell,pxa168-usb-phy", + .data = (void *)PXA_USB_PHY_PXA168, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, pxa_usb_phy_of_match); + +static int pxa_usb_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pxa_usb_phy *pxa_usb_phy; + struct phy_provider *provider; + const struct of_device_id *of_id; + + pxa_usb_phy = devm_kzalloc(dev, sizeof(struct pxa_usb_phy), GFP_KERNEL); + if (!pxa_usb_phy) + return -ENOMEM; + + of_id = of_match_node(pxa_usb_phy_of_match, dev->of_node); + if (of_id) + pxa_usb_phy->version = (enum pxa_usb_phy_version)of_id->data; + else + pxa_usb_phy->version = PXA_USB_PHY_MMP2; + + pxa_usb_phy->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pxa_usb_phy->base)) { + dev_err(dev, "failed to remap PHY regs\n"); + return PTR_ERR(pxa_usb_phy->base); + } + + pxa_usb_phy->phy = devm_phy_create(dev, NULL, &pxa_usb_phy_ops); + if (IS_ERR(pxa_usb_phy->phy)) { + dev_err(dev, "failed to create PHY\n"); + return PTR_ERR(pxa_usb_phy->phy); + } + + phy_set_drvdata(pxa_usb_phy->phy, pxa_usb_phy); + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(provider)) { + dev_err(dev, "failed to register PHY provider\n"); + return PTR_ERR(provider); + } + + if (!dev->of_node) { + phy_create_lookup(pxa_usb_phy->phy, "usb", "mv-udc"); + phy_create_lookup(pxa_usb_phy->phy, "usb", "pxa-u2oehci"); + phy_create_lookup(pxa_usb_phy->phy, "usb", "mv-otg"); + } + + dev_info(dev, "Marvell PXA USB PHY"); + return 0; +} + +static struct platform_driver pxa_usb_phy_driver = { + .probe = pxa_usb_phy_probe, + .driver = { + .name = "pxa-usb-phy", + .of_match_table = pxa_usb_phy_of_match, + }, +}; +module_platform_driver(pxa_usb_phy_driver); + +MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); +MODULE_DESCRIPTION("Marvell PXA USB PHY Driver"); +MODULE_LICENSE("GPL v2"); |