summaryrefslogtreecommitdiffstats
path: root/drivers/phy/marvell
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/phy/marvell')
-rw-r--r--drivers/phy/marvell/Kconfig118
-rw-r--r--drivers/phy/marvell/Makefile13
-rw-r--r--drivers/phy/marvell/phy-armada375-usb2.c149
-rw-r--r--drivers/phy/marvell/phy-armada38x-comphy.c270
-rw-r--r--drivers/phy/marvell/phy-berlin-sata.c297
-rw-r--r--drivers/phy/marvell/phy-berlin-usb.c211
-rw-r--r--drivers/phy/marvell/phy-mmp3-usb.c291
-rw-r--r--drivers/phy/marvell/phy-mvebu-a3700-comphy.c337
-rw-r--r--drivers/phy/marvell/phy-mvebu-a3700-utmi.c270
-rw-r--r--drivers/phy/marvell/phy-mvebu-cp110-comphy.c1103
-rw-r--r--drivers/phy/marvell/phy-mvebu-sata.c129
-rw-r--r--drivers/phy/marvell/phy-pxa-28nm-hsic.c211
-rw-r--r--drivers/phy/marvell/phy-pxa-28nm-usb2.c343
-rw-r--r--drivers/phy/marvell/phy-pxa-usb.c345
14 files changed, 4087 insertions, 0 deletions
diff --git a/drivers/phy/marvell/Kconfig b/drivers/phy/marvell/Kconfig
new file mode 100644
index 000000000..8ab9031c9
--- /dev/null
+++ b/drivers/phy/marvell/Kconfig
@@ -0,0 +1,118 @@
+# 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_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.
diff --git a/drivers/phy/marvell/Makefile b/drivers/phy/marvell/Makefile
new file mode 100644
index 000000000..5a106b154
--- /dev/null
+++ b/drivers/phy/marvell/Makefile
@@ -0,0 +1,13 @@
+# 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_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_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..fa5dc9462
--- /dev/null
+++ b/drivers/phy/marvell/phy-armada375-usb2.c
@@ -0,0 +1,149 @@
+// 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 resource *res;
+ struct armada375_cluster_phy *cluster_phy;
+
+ cluster_phy = devm_kzalloc(dev, sizeof(*cluster_phy), GFP_KERNEL);
+ if (!cluster_phy)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ usb_cluster_base = devm_ioremap_resource(&pdev->dev, res);
+ 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..a43df6300
--- /dev/null
+++ b/drivers/phy/marvell/phy-berlin-usb.c
@@ -0,0 +1,211 @@
+// 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 resource *res;
+ struct phy *phy;
+ struct phy_provider *phy_provider;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ 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);
+
+ 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-usb.c b/drivers/phy/marvell/phy-mmp3-usb.c
new file mode 100644
index 000000000..499869595
--- /dev/null
+++ b/drivers/phy/marvell/phy-mmp3-usb.c
@@ -0,0 +1,291 @@
+// 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 resource *resource;
+ 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;
+
+ resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ mmp3_usb_phy->base = devm_ioremap_resource(dev, resource);
+ 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..810f25a47
--- /dev/null
+++ b/drivers/phy/marvell/phy-mvebu-a3700-comphy.c
@@ -0,0 +1,337 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Marvell
+ *
+ * Authors:
+ * Evan Wang <xswang@marvell.com>
+ * Miquèl Raynal <miquel.raynal@bootlin.com>
+ *
+ * Structure inspired from phy-mvebu-cp110-comphy.c written by Antoine Tenart.
+ * SMC call initial support done by Grzegorz Jaszczyk.
+ */
+
+#include <linux/arm-smccc.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>
+
+#define MVEBU_A3700_COMPHY_LANES 3
+#define MVEBU_A3700_COMPHY_PORTS 2
+
+/* COMPHY Fast SMC function identifiers */
+#define COMPHY_SIP_POWER_ON 0x82000001
+#define COMPHY_SIP_POWER_OFF 0x82000002
+#define COMPHY_SIP_PLL_LOCK 0x82000003
+
+#define COMPHY_FW_MODE_SATA 0x1
+#define COMPHY_FW_MODE_SGMII 0x2
+#define COMPHY_FW_MODE_HS_SGMII 0x3
+#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
+#define COMPHY_FW_MODE_SFI 0x9
+#define COMPHY_FW_MODE_USB3 0xa
+
+#define COMPHY_FW_SPEED_1_25G 0 /* SGMII 1G */
+#define COMPHY_FW_SPEED_2_5G 1
+#define COMPHY_FW_SPEED_3_125G 2 /* SGMII 2.5G */
+#define COMPHY_FW_SPEED_5G 3
+#define COMPHY_FW_SPEED_5_15625G 4 /* XFI 5G */
+#define COMPHY_FW_SPEED_6G 5
+#define COMPHY_FW_SPEED_10_3125G 6 /* XFI 10G */
+#define COMPHY_FW_SPEED_MAX 0x3F
+
+#define COMPHY_FW_MODE(mode) ((mode) << 12)
+#define COMPHY_FW_NET(mode, idx, speed) (COMPHY_FW_MODE(mode) | \
+ ((idx) << 8) | \
+ ((speed) << 2))
+#define COMPHY_FW_PCIE(mode, idx, speed, width) (COMPHY_FW_NET(mode, idx, speed) | \
+ ((width) << 18))
+
+struct mvebu_a3700_comphy_conf {
+ unsigned int lane;
+ enum phy_mode mode;
+ int submode;
+ unsigned int port;
+ u32 fw_mode;
+};
+
+#define MVEBU_A3700_COMPHY_CONF(_lane, _mode, _smode, _port, _fw) \
+ { \
+ .lane = _lane, \
+ .mode = _mode, \
+ .submode = _smode, \
+ .port = _port, \
+ .fw_mode = _fw, \
+ }
+
+#define MVEBU_A3700_COMPHY_CONF_GEN(_lane, _mode, _port, _fw) \
+ MVEBU_A3700_COMPHY_CONF(_lane, _mode, PHY_INTERFACE_MODE_NA, _port, _fw)
+
+#define MVEBU_A3700_COMPHY_CONF_ETH(_lane, _smode, _port, _fw) \
+ MVEBU_A3700_COMPHY_CONF(_lane, PHY_MODE_ETHERNET, _smode, _port, _fw)
+
+static const struct mvebu_a3700_comphy_conf mvebu_a3700_comphy_modes[] = {
+ /* lane 0 */
+ MVEBU_A3700_COMPHY_CONF_GEN(0, PHY_MODE_USB_HOST_SS, 0,
+ COMPHY_FW_MODE_USB3H),
+ MVEBU_A3700_COMPHY_CONF_ETH(0, PHY_INTERFACE_MODE_SGMII, 1,
+ COMPHY_FW_MODE_SGMII),
+ MVEBU_A3700_COMPHY_CONF_ETH(0, PHY_INTERFACE_MODE_2500BASEX, 1,
+ COMPHY_FW_MODE_HS_SGMII),
+ /* lane 1 */
+ MVEBU_A3700_COMPHY_CONF_GEN(1, PHY_MODE_PCIE, 0,
+ COMPHY_FW_MODE_PCIE),
+ MVEBU_A3700_COMPHY_CONF_ETH(1, PHY_INTERFACE_MODE_SGMII, 0,
+ COMPHY_FW_MODE_SGMII),
+ MVEBU_A3700_COMPHY_CONF_ETH(1, PHY_INTERFACE_MODE_2500BASEX, 0,
+ COMPHY_FW_MODE_HS_SGMII),
+ /* lane 2 */
+ MVEBU_A3700_COMPHY_CONF_GEN(2, PHY_MODE_SATA, 0,
+ COMPHY_FW_MODE_SATA),
+ MVEBU_A3700_COMPHY_CONF_GEN(2, PHY_MODE_USB_HOST_SS, 0,
+ COMPHY_FW_MODE_USB3H),
+};
+
+struct mvebu_a3700_comphy_lane {
+ struct device *dev;
+ unsigned int id;
+ enum phy_mode mode;
+ int submode;
+ int port;
+};
+
+static int mvebu_a3700_comphy_smc(unsigned long function, unsigned long lane,
+ unsigned long mode)
+{
+ struct arm_smccc_res res;
+ s32 ret;
+
+ arm_smccc_smc(function, lane, mode, 0, 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_a3700_comphy_get_fw_mode(int lane, int port,
+ 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 -EINVAL;
+
+ for (i = 0; i < n; i++) {
+ if (mvebu_a3700_comphy_modes[i].lane == lane &&
+ mvebu_a3700_comphy_modes[i].port == port &&
+ mvebu_a3700_comphy_modes[i].mode == mode &&
+ mvebu_a3700_comphy_modes[i].submode == submode)
+ break;
+ }
+
+ if (i == n)
+ return -EINVAL;
+
+ return mvebu_a3700_comphy_modes[i].fw_mode;
+}
+
+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);
+ int fw_mode;
+
+ if (submode == PHY_INTERFACE_MODE_1000BASEX)
+ submode = PHY_INTERFACE_MODE_SGMII;
+
+ fw_mode = mvebu_a3700_comphy_get_fw_mode(lane->id, lane->port, mode,
+ submode);
+ if (fw_mode < 0) {
+ dev_err(lane->dev, "invalid COMPHY mode\n");
+ return fw_mode;
+ }
+
+ /* 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);
+ u32 fw_param;
+ int fw_mode;
+ int ret;
+
+ fw_mode = mvebu_a3700_comphy_get_fw_mode(lane->id, lane->port,
+ lane->mode, lane->submode);
+ if (fw_mode < 0) {
+ dev_err(lane->dev, "invalid COMPHY mode\n");
+ return fw_mode;
+ }
+
+ switch (lane->mode) {
+ case PHY_MODE_USB_HOST_SS:
+ dev_dbg(lane->dev, "set lane %d to USB3 host mode\n", lane->id);
+ fw_param = COMPHY_FW_MODE(fw_mode);
+ break;
+ case PHY_MODE_SATA:
+ dev_dbg(lane->dev, "set lane %d to SATA mode\n", lane->id);
+ fw_param = COMPHY_FW_MODE(fw_mode);
+ break;
+ case PHY_MODE_ETHERNET:
+ switch (lane->submode) {
+ case PHY_INTERFACE_MODE_SGMII:
+ dev_dbg(lane->dev, "set lane %d to SGMII mode\n",
+ lane->id);
+ fw_param = COMPHY_FW_NET(fw_mode, lane->port,
+ COMPHY_FW_SPEED_1_25G);
+ break;
+ case PHY_INTERFACE_MODE_2500BASEX:
+ dev_dbg(lane->dev, "set lane %d to HS SGMII mode\n",
+ lane->id);
+ fw_param = COMPHY_FW_NET(fw_mode, lane->port,
+ COMPHY_FW_SPEED_3_125G);
+ break;
+ default:
+ dev_err(lane->dev, "unsupported PHY submode (%d)\n",
+ lane->submode);
+ return -ENOTSUPP;
+ }
+ break;
+ case PHY_MODE_PCIE:
+ dev_dbg(lane->dev, "set lane %d to PCIe mode\n", lane->id);
+ fw_param = COMPHY_FW_PCIE(fw_mode, lane->port,
+ COMPHY_FW_SPEED_5G,
+ phy->attrs.bus_width);
+ break;
+ default:
+ dev_err(lane->dev, "unsupported PHY mode (%d)\n", lane->mode);
+ return -ENOTSUPP;
+ }
+
+ ret = mvebu_a3700_comphy_smc(COMPHY_SIP_POWER_ON, lane->id, fw_param);
+ if (ret == -EOPNOTSUPP)
+ dev_err(lane->dev,
+ "unsupported SMC call, try updating your firmware\n");
+
+ return ret;
+}
+
+static int mvebu_a3700_comphy_power_off(struct phy *phy)
+{
+ struct mvebu_a3700_comphy_lane *lane = phy_get_drvdata(phy);
+
+ return mvebu_a3700_comphy_smc(COMPHY_SIP_POWER_OFF, lane->id, 0);
+}
+
+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;
+ struct phy *phy;
+
+ if (WARN_ON(args->args[0] >= MVEBU_A3700_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_a3700_comphy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *provider;
+ struct device_node *child;
+
+ 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 >= MVEBU_A3700_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);
+ 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->dev = &pdev->dev;
+ lane->mode = PHY_MODE_INVALID;
+ lane->submode = PHY_INTERFACE_MODE_NA;
+ lane->id = lane_id;
+ lane->port = -1;
+ phy_set_drvdata(phy, lane);
+ }
+
+ 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_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..53ad127b1
--- /dev/null
+++ b/drivers/phy/marvell/phy-mvebu-cp110-comphy.c
@@ -0,0 +1,1103 @@
+// 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_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_HS_SGMII 0x3 /* SGMII 2.5G */
+#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_HS_SGMII),
+ 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_HS_SGMII),
+ /* 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_HS_SGMII),
+ ETH_CONF(2, 0, PHY_INTERFACE_MODE_RXAUI, 0x1, COMPHY_FW_MODE_RXAUI),
+ 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_HS_SGMII),
+ 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_HS_SGMII),
+ 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_HS_SGMII),
+ 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_HS_SGMII),
+ 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_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-sata.c b/drivers/phy/marvell/phy-mvebu-sata.c
new file mode 100644
index 000000000..3c01b5dce
--- /dev/null
+++ b/drivers/phy/marvell/phy-mvebu-sata.c
@@ -0,0 +1,129 @@
+// 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 resource *res;
+ struct priv *priv;
+ struct phy *phy;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ 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);
+
+ 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..31b43d2ee
--- /dev/null
+++ b/drivers/phy/marvell/phy-pxa-28nm-hsic.c
@@ -0,0 +1,211 @@
+// 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;
+ struct resource *r;
+
+ 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);
+ }
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ mv_phy->base = devm_ioremap_resource(&pdev->dev, r);
+ 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..a175ae915
--- /dev/null
+++ b/drivers/phy/marvell/phy-pxa-28nm-usb2.c
@@ -0,0 +1,343 @@
+// 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;
+ struct resource *r;
+
+ 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);
+ }
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ mv_phy->base = devm_ioremap_resource(&pdev->dev, r);
+ 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..87ff7550b
--- /dev/null
+++ b/drivers/phy/marvell/phy-pxa-usb.c
@@ -0,0 +1,345 @@
+// 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 resource *resource;
+ 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;
+
+ resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ pxa_usb_phy->base = devm_ioremap_resource(dev, resource);
+ 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");