382 lines
11 KiB
Diff
382 lines
11 KiB
Diff
From 9695375a3f4a604406f2e61f2b735eca1de931ed Mon Sep 17 00:00:00 2001
|
|
From: Miquel Raynal <miquel.raynal@bootlin.com>
|
|
Date: Tue, 8 Jan 2019 17:31:20 +0100
|
|
Subject: [PATCH] phy: add A3700 COMPHY support
|
|
|
|
Add a driver to support COMPHY, a hardware block providing shared
|
|
serdes PHYs on Marvell Armada 3700. This driver uses SMC calls and
|
|
rely on having an up-to-date firmware.
|
|
|
|
SATA, PCie and USB3 host mode have been tested successfully with an
|
|
ESPRESSObin. (HS)SGMII mode cannot be tested with this platform.
|
|
|
|
Evan worked on the original driver structure and Grzegorz on the SMC
|
|
calls rework. The structure of this driver has been copied from
|
|
Antoine Tenart work on CP110 COMPHY driver.
|
|
|
|
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
|
|
Co-developed-by: Evan Wang <xswang@marvell.com>
|
|
Signed-off-by: Evan Wang <xswang@marvell.com>
|
|
Co-developed-by: Grzegorz Jaszczyk <jaz@semihalf.com>
|
|
Signed-off-by: Grzegorz Jaszczyk <jaz@semihalf.com>
|
|
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
|
|
---
|
|
drivers/phy/marvell/Kconfig | 12 +
|
|
drivers/phy/marvell/Makefile | 1 +
|
|
drivers/phy/marvell/phy-mvebu-a3700-comphy.c | 318 +++++++++++++++++++++++++++
|
|
3 files changed, 331 insertions(+)
|
|
create mode 100644 drivers/phy/marvell/phy-mvebu-a3700-comphy.c
|
|
|
|
--- a/drivers/phy/marvell/Kconfig
|
|
+++ b/drivers/phy/marvell/Kconfig
|
|
@@ -21,6 +21,18 @@ config PHY_BERLIN_USB
|
|
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_CP110_COMPHY
|
|
tristate "Marvell CP110 comphy driver"
|
|
depends on ARCH_MVEBU || COMPILE_TEST
|
|
--- a/drivers/phy/marvell/Makefile
|
|
+++ b/drivers/phy/marvell/Makefile
|
|
@@ -2,6 +2,7 @@
|
|
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_MVEBU_A3700_COMPHY) += phy-mvebu-a3700-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
|
|
--- /dev/null
|
|
+++ b/drivers/phy/marvell/phy-mvebu-a3700-comphy.c
|
|
@@ -0,0 +1,318 @@
|
|
+// 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;
|
|
+
|
|
+ arm_smccc_smc(function, lane, mode, 0, 0, 0, 0, 0, &res);
|
|
+
|
|
+ return res.a0;
|
|
+}
|
|
+
|
|
+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;
|
|
+
|
|
+ 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;
|
|
+ }
|
|
+
|
|
+ return mvebu_a3700_comphy_smc(COMPHY_SIP_POWER_ON, lane->id, fw_param);
|
|
+}
|
|
+
|
|
+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)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ phy = devm_phy_create(&pdev->dev, child,
|
|
+ &mvebu_a3700_comphy_ops);
|
|
+ if (IS_ERR(phy))
|
|
+ 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");
|