summaryrefslogtreecommitdiffstats
path: root/drivers/phy/amlogic
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
commitace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch)
treeb2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/phy/amlogic
parentInitial commit. (diff)
downloadlinux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz
linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/phy/amlogic')
-rw-r--r--drivers/phy/amlogic/Kconfig107
-rw-r--r--drivers/phy/amlogic/Makefile10
-rw-r--r--drivers/phy/amlogic/phy-meson-axg-mipi-dphy.c412
-rw-r--r--drivers/phy/amlogic/phy-meson-axg-mipi-pcie-analog.c256
-rw-r--r--drivers/phy/amlogic/phy-meson-axg-pcie.c191
-rw-r--r--drivers/phy/amlogic/phy-meson-g12a-mipi-dphy-analog.c172
-rw-r--r--drivers/phy/amlogic/phy-meson-g12a-usb2.c383
-rw-r--r--drivers/phy/amlogic/phy-meson-g12a-usb3-pcie.c447
-rw-r--r--drivers/phy/amlogic/phy-meson-gxl-usb2.c301
-rw-r--r--drivers/phy/amlogic/phy-meson8-hdmi-tx.c160
-rw-r--r--drivers/phy/amlogic/phy-meson8b-usb2.c337
11 files changed, 2776 insertions, 0 deletions
diff --git a/drivers/phy/amlogic/Kconfig b/drivers/phy/amlogic/Kconfig
new file mode 100644
index 0000000000..ce7ba3eb2a
--- /dev/null
+++ b/drivers/phy/amlogic/Kconfig
@@ -0,0 +1,107 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Phy drivers for Amlogic platforms
+#
+config PHY_MESON8_HDMI_TX
+ tristate "Meson8, Meson8b and Meson8m2 HDMI TX PHY driver"
+ depends on (ARCH_MESON && ARM) || COMPILE_TEST
+ depends on OF
+ select MFD_SYSCON
+ help
+ Enable this to support the HDMI TX PHYs found in Meson8,
+ Meson8b and Meson8m2 SoCs.
+ If unsure, say N.
+
+config PHY_MESON8B_USB2
+ tristate "Meson8, Meson8b, Meson8m2 and GXBB USB2 PHY driver"
+ default ARCH_MESON
+ depends on OF && (ARCH_MESON || COMPILE_TEST)
+ depends on USB_SUPPORT
+ select USB_COMMON
+ select GENERIC_PHY
+ select REGMAP_MMIO
+ help
+ Enable this to support the Meson USB2 PHYs found in Meson8,
+ Meson8b and GXBB SoCs.
+ If unsure, say N.
+
+config PHY_MESON_GXL_USB2
+ tristate "Meson GXL and GXM USB2 PHY drivers"
+ default ARCH_MESON
+ depends on OF && (ARCH_MESON || COMPILE_TEST)
+ depends on USB_SUPPORT
+ select GENERIC_PHY
+ select REGMAP_MMIO
+ help
+ Enable this to support the Meson USB2 PHYs found in Meson
+ GXL and GXM SoCs.
+ If unsure, say N.
+
+config PHY_MESON_G12A_MIPI_DPHY_ANALOG
+ tristate "Meson G12A MIPI Analog DPHY driver"
+ default ARCH_MESON
+ depends on OF && (ARCH_MESON || COMPILE_TEST)
+ select GENERIC_PHY
+ select MFD_SYSCON
+ select GENERIC_PHY_MIPI_DPHY
+ help
+ Enable this to support the Meson MIPI Analog DPHY found in Meson G12A
+ SoCs.
+ If unsure, say N.
+
+config PHY_MESON_G12A_USB2
+ tristate "Meson G12A USB2 PHY driver"
+ default ARCH_MESON
+ depends on OF && (ARCH_MESON || COMPILE_TEST)
+ select GENERIC_PHY
+ select REGMAP_MMIO
+ help
+ Enable this to support the Meson USB2 PHYs found in Meson
+ G12A SoCs.
+ If unsure, say N.
+
+config PHY_MESON_G12A_USB3_PCIE
+ tristate "Meson G12A USB3+PCIE Combo PHY driver"
+ default ARCH_MESON
+ depends on OF && (ARCH_MESON || COMPILE_TEST)
+ select GENERIC_PHY
+ select REGMAP_MMIO
+ help
+ Enable this to support the Meson USB3 + PCIE Combo PHY found
+ in Meson G12A SoCs.
+ If unsure, say N.
+
+config PHY_MESON_AXG_PCIE
+ tristate "Meson AXG PCIE PHY driver"
+ default ARCH_MESON
+ depends on OF && (ARCH_MESON || COMPILE_TEST)
+ select GENERIC_PHY
+ select REGMAP_MMIO
+ help
+ Enable this to support the Meson MIPI + PCIE PHY found
+ in Meson AXG SoCs.
+ If unsure, say N.
+
+config PHY_MESON_AXG_MIPI_PCIE_ANALOG
+ tristate "Meson AXG MIPI + PCIE analog PHY driver"
+ default ARCH_MESON
+ depends on OF && (ARCH_MESON || COMPILE_TEST)
+ select GENERIC_PHY
+ select REGMAP_MMIO
+ select GENERIC_PHY_MIPI_DPHY
+ help
+ Enable this to support the Meson MIPI + PCIE analog PHY
+ found in Meson AXG SoCs.
+ If unsure, say N.
+
+config PHY_MESON_AXG_MIPI_DPHY
+ tristate "Meson AXG MIPI DPHY driver"
+ default ARCH_MESON
+ depends on OF && (ARCH_MESON || COMPILE_TEST)
+ select GENERIC_PHY
+ select REGMAP_MMIO
+ select GENERIC_PHY_MIPI_DPHY
+ help
+ Enable this to support the Meson MIPI DPHY found in Meson AXG
+ SoCs.
+ If unsure, say N.
diff --git a/drivers/phy/amlogic/Makefile b/drivers/phy/amlogic/Makefile
new file mode 100644
index 0000000000..91e3b9790c
--- /dev/null
+++ b/drivers/phy/amlogic/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_PHY_MESON8_HDMI_TX) += phy-meson8-hdmi-tx.o
+obj-$(CONFIG_PHY_MESON8B_USB2) += phy-meson8b-usb2.o
+obj-$(CONFIG_PHY_MESON_GXL_USB2) += phy-meson-gxl-usb2.o
+obj-$(CONFIG_PHY_MESON_G12A_USB2) += phy-meson-g12a-usb2.o
+obj-$(CONFIG_PHY_MESON_G12A_USB3_PCIE) += phy-meson-g12a-usb3-pcie.o
+obj-$(CONFIG_PHY_MESON_G12A_MIPI_DPHY_ANALOG) += phy-meson-g12a-mipi-dphy-analog.o
+obj-$(CONFIG_PHY_MESON_AXG_PCIE) += phy-meson-axg-pcie.o
+obj-$(CONFIG_PHY_MESON_AXG_MIPI_PCIE_ANALOG) += phy-meson-axg-mipi-pcie-analog.o
+obj-$(CONFIG_PHY_MESON_AXG_MIPI_DPHY) += phy-meson-axg-mipi-dphy.o
diff --git a/drivers/phy/amlogic/phy-meson-axg-mipi-dphy.c b/drivers/phy/amlogic/phy-meson-axg-mipi-dphy.c
new file mode 100644
index 0000000000..08a86962d9
--- /dev/null
+++ b/drivers/phy/amlogic/phy-meson-axg-mipi-dphy.c
@@ -0,0 +1,412 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Meson AXG MIPI DPHY driver
+ *
+ * Copyright (C) 2018 Amlogic, Inc. All rights reserved
+ * Copyright (C) 2020 BayLibre, SAS
+ * Author: Neil Armstrong <narmstrong@baylibre.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+/* [31] soft reset for the phy.
+ * 1: reset. 0: dessert the reset.
+ * [30] clock lane soft reset.
+ * [29] data byte lane 3 soft reset.
+ * [28] data byte lane 2 soft reset.
+ * [27] data byte lane 1 soft reset.
+ * [26] data byte lane 0 soft reset.
+ * [25] mipi dsi pll clock selection.
+ * 1: clock from fixed 850Mhz clock source. 0: from VID2 PLL.
+ * [12] mipi HSbyteclk enable.
+ * [11] mipi divider clk selection.
+ * 1: select the mipi DDRCLKHS from clock divider.
+ * 0: from PLL clock.
+ * [10] mipi clock divider control.
+ * 1: /4. 0: /2.
+ * [9] mipi divider output enable.
+ * [8] mipi divider counter enable.
+ * [7] PLL clock enable.
+ * [5] LPDT data endian.
+ * 1 = transfer the high bit first. 0 : transfer the low bit first.
+ * [4] HS data endian.
+ * [3] force data byte lane in stop mode.
+ * [2] force data byte lane 0 in receiver mode.
+ * [1] write 1 to sync the txclkesc input. the internal logic have to
+ * use txclkesc to decide Txvalid and Txready.
+ * [0] enalbe the MIPI DPHY TxDDRClk.
+ */
+#define MIPI_DSI_PHY_CTRL 0x0
+
+/* [31] clk lane tx_hs_en control selection.
+ * 1: from register. 0: use clk lane state machine.
+ * [30] register bit for clock lane tx_hs_en.
+ * [29] clk lane tx_lp_en contrl selection.
+ * 1: from register. 0: from clk lane state machine.
+ * [28] register bit for clock lane tx_lp_en.
+ * [27] chan0 tx_hs_en control selection.
+ * 1: from register. 0: from chan0 state machine.
+ * [26] register bit for chan0 tx_hs_en.
+ * [25] chan0 tx_lp_en control selection.
+ * 1: from register. 0: from chan0 state machine.
+ * [24] register bit from chan0 tx_lp_en.
+ * [23] chan0 rx_lp_en control selection.
+ * 1: from register. 0: from chan0 state machine.
+ * [22] register bit from chan0 rx_lp_en.
+ * [21] chan0 contention detection enable control selection.
+ * 1: from register. 0: from chan0 state machine.
+ * [20] register bit from chan0 contention dectection enable.
+ * [19] chan1 tx_hs_en control selection.
+ * 1: from register. 0: from chan0 state machine.
+ * [18] register bit for chan1 tx_hs_en.
+ * [17] chan1 tx_lp_en control selection.
+ * 1: from register. 0: from chan0 state machine.
+ * [16] register bit from chan1 tx_lp_en.
+ * [15] chan2 tx_hs_en control selection.
+ * 1: from register. 0: from chan0 state machine.
+ * [14] register bit for chan2 tx_hs_en.
+ * [13] chan2 tx_lp_en control selection.
+ * 1: from register. 0: from chan0 state machine.
+ * [12] register bit from chan2 tx_lp_en.
+ * [11] chan3 tx_hs_en control selection.
+ * 1: from register. 0: from chan0 state machine.
+ * [10] register bit for chan3 tx_hs_en.
+ * [9] chan3 tx_lp_en control selection.
+ * 1: from register. 0: from chan0 state machine.
+ * [8] register bit from chan3 tx_lp_en.
+ * [4] clk chan power down. this bit is also used as the power down
+ * of the whole MIPI_DSI_PHY.
+ * [3] chan3 power down.
+ * [2] chan2 power down.
+ * [1] chan1 power down.
+ * [0] chan0 power down.
+ */
+#define MIPI_DSI_CHAN_CTRL 0x4
+
+/* [24] rx turn watch dog triggered.
+ * [23] rx esc watchdog triggered.
+ * [22] mbias ready.
+ * [21] txclkesc synced and ready.
+ * [20:17] clk lane state. {mbias_ready, tx_stop, tx_ulps, tx_hs_active}
+ * [16:13] chan3 state{0, tx_stop, tx_ulps, tx_hs_active}
+ * [12:9] chan2 state.{0, tx_stop, tx_ulps, tx_hs_active}
+ * [8:5] chan1 state. {0, tx_stop, tx_ulps, tx_hs_active}
+ * [4:0] chan0 state. {TX_STOP, tx_ULPS, hs_active, direction, rxulpsesc}
+ */
+#define MIPI_DSI_CHAN_STS 0x8
+
+/* [31:24] TCLK_PREPARE.
+ * [23:16] TCLK_ZERO.
+ * [15:8] TCLK_POST.
+ * [7:0] TCLK_TRAIL.
+ */
+#define MIPI_DSI_CLK_TIM 0xc
+
+/* [31:24] THS_PREPARE.
+ * [23:16] THS_ZERO.
+ * [15:8] THS_TRAIL.
+ * [7:0] THS_EXIT.
+ */
+#define MIPI_DSI_HS_TIM 0x10
+
+/* [31:24] tTA_GET.
+ * [23:16] tTA_GO.
+ * [15:8] tTA_SURE.
+ * [7:0] tLPX.
+ */
+#define MIPI_DSI_LP_TIM 0x14
+
+/* wait time to MIPI DIS analog ready. */
+#define MIPI_DSI_ANA_UP_TIM 0x18
+
+/* TINIT. */
+#define MIPI_DSI_INIT_TIM 0x1c
+
+/* TWAKEUP. */
+#define MIPI_DSI_WAKEUP_TIM 0x20
+
+/* when in RxULPS check state, after the logic enable the analog,
+ * how long we should wait to check the lP state .
+ */
+#define MIPI_DSI_LPOK_TIM 0x24
+
+/* Watchdog for RX low power state no finished. */
+#define MIPI_DSI_LP_WCHDOG 0x28
+
+/* tMBIAS, after send power up signals to analog,
+ * how long we should wait for analog powered up.
+ */
+#define MIPI_DSI_ANA_CTRL 0x2c
+
+/* [31:8] reserved for future.
+ * [7:0] tCLK_PRE.
+ */
+#define MIPI_DSI_CLK_TIM1 0x30
+
+/* watchdog for turn around waiting time. */
+#define MIPI_DSI_TURN_WCHDOG 0x34
+
+/* When in RxULPS state, how frequency we should to check
+ * if the TX side out of ULPS state.
+ */
+#define MIPI_DSI_ULPS_CHECK 0x38
+#define MIPI_DSI_TEST_CTRL0 0x3c
+#define MIPI_DSI_TEST_CTRL1 0x40
+
+struct phy_meson_axg_mipi_dphy_priv {
+ struct device *dev;
+ struct regmap *regmap;
+ struct clk *clk;
+ struct reset_control *reset;
+ struct phy *analog;
+ struct phy_configure_opts_mipi_dphy config;
+};
+
+static const struct regmap_config phy_meson_axg_mipi_dphy_regmap_conf = {
+ .reg_bits = 8,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = MIPI_DSI_TEST_CTRL1,
+};
+
+static int phy_meson_axg_mipi_dphy_init(struct phy *phy)
+{
+ struct phy_meson_axg_mipi_dphy_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ ret = phy_init(priv->analog);
+ if (ret)
+ return ret;
+
+ ret = reset_control_reset(priv->reset);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int phy_meson_axg_mipi_dphy_configure(struct phy *phy,
+ union phy_configure_opts *opts)
+{
+ struct phy_meson_axg_mipi_dphy_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ ret = phy_mipi_dphy_config_validate(&opts->mipi_dphy);
+ if (ret)
+ return ret;
+
+ ret = phy_configure(priv->analog, opts);
+ if (ret)
+ return ret;
+
+ memcpy(&priv->config, opts, sizeof(priv->config));
+
+ return 0;
+}
+
+static int phy_meson_axg_mipi_dphy_power_on(struct phy *phy)
+{
+ struct phy_meson_axg_mipi_dphy_priv *priv = phy_get_drvdata(phy);
+ int ret;
+ unsigned long temp;
+
+ ret = phy_power_on(priv->analog);
+ if (ret)
+ return ret;
+
+ /* enable phy clock */
+ regmap_write(priv->regmap, MIPI_DSI_PHY_CTRL, 0x1);
+ regmap_write(priv->regmap, MIPI_DSI_PHY_CTRL,
+ BIT(0) | /* enable the DSI PLL clock . */
+ BIT(7) | /* enable pll clock which connected to DDR clock path */
+ BIT(8)); /* enable the clock divider counter */
+
+ /* enable the divider clock out */
+ regmap_update_bits(priv->regmap, MIPI_DSI_PHY_CTRL, BIT(9), BIT(9));
+
+ /* enable the byte clock generation. */
+ regmap_update_bits(priv->regmap, MIPI_DSI_PHY_CTRL, BIT(12), BIT(12));
+ regmap_update_bits(priv->regmap, MIPI_DSI_PHY_CTRL, BIT(31), BIT(31));
+ regmap_update_bits(priv->regmap, MIPI_DSI_PHY_CTRL, BIT(31), 0);
+
+ /* Calculate lanebyteclk period in ps */
+ temp = (1000000 * 100) / (priv->config.hs_clk_rate / 1000);
+ temp = temp * 8 * 10;
+
+ regmap_write(priv->regmap, MIPI_DSI_CLK_TIM,
+ DIV_ROUND_UP(priv->config.clk_trail, temp) |
+ (DIV_ROUND_UP(priv->config.clk_post +
+ priv->config.hs_trail, temp) << 8) |
+ (DIV_ROUND_UP(priv->config.clk_zero, temp) << 16) |
+ (DIV_ROUND_UP(priv->config.clk_prepare, temp) << 24));
+ regmap_write(priv->regmap, MIPI_DSI_CLK_TIM1,
+ DIV_ROUND_UP(priv->config.clk_pre, BITS_PER_BYTE));
+
+ regmap_write(priv->regmap, MIPI_DSI_HS_TIM,
+ DIV_ROUND_UP(priv->config.hs_exit, temp) |
+ (DIV_ROUND_UP(priv->config.hs_trail, temp) << 8) |
+ (DIV_ROUND_UP(priv->config.hs_zero, temp) << 16) |
+ (DIV_ROUND_UP(priv->config.hs_prepare, temp) << 24));
+
+ regmap_write(priv->regmap, MIPI_DSI_LP_TIM,
+ DIV_ROUND_UP(priv->config.lpx, temp) |
+ (DIV_ROUND_UP(priv->config.ta_sure, temp) << 8) |
+ (DIV_ROUND_UP(priv->config.ta_go, temp) << 16) |
+ (DIV_ROUND_UP(priv->config.ta_get, temp) << 24));
+
+ regmap_write(priv->regmap, MIPI_DSI_ANA_UP_TIM, 0x0100);
+ regmap_write(priv->regmap, MIPI_DSI_INIT_TIM,
+ DIV_ROUND_UP(priv->config.init * NSEC_PER_MSEC, temp));
+ regmap_write(priv->regmap, MIPI_DSI_WAKEUP_TIM,
+ DIV_ROUND_UP(priv->config.wakeup * NSEC_PER_MSEC, temp));
+ regmap_write(priv->regmap, MIPI_DSI_LPOK_TIM, 0x7C);
+ regmap_write(priv->regmap, MIPI_DSI_ULPS_CHECK, 0x927C);
+ regmap_write(priv->regmap, MIPI_DSI_LP_WCHDOG, 0x1000);
+ regmap_write(priv->regmap, MIPI_DSI_TURN_WCHDOG, 0x1000);
+
+ /* Powerup the analog circuit */
+ switch (priv->config.lanes) {
+ case 1:
+ regmap_write(priv->regmap, MIPI_DSI_CHAN_CTRL, 0xe);
+ break;
+ case 2:
+ regmap_write(priv->regmap, MIPI_DSI_CHAN_CTRL, 0xc);
+ break;
+ case 3:
+ regmap_write(priv->regmap, MIPI_DSI_CHAN_CTRL, 0x8);
+ break;
+ case 4:
+ default:
+ regmap_write(priv->regmap, MIPI_DSI_CHAN_CTRL, 0);
+ break;
+ }
+
+ /* Trigger a sync active for esc_clk */
+ regmap_update_bits(priv->regmap, MIPI_DSI_PHY_CTRL, BIT(1), BIT(1));
+
+ return 0;
+}
+
+static int phy_meson_axg_mipi_dphy_power_off(struct phy *phy)
+{
+ struct phy_meson_axg_mipi_dphy_priv *priv = phy_get_drvdata(phy);
+
+ regmap_write(priv->regmap, MIPI_DSI_CHAN_CTRL, 0xf);
+ regmap_write(priv->regmap, MIPI_DSI_PHY_CTRL, BIT(31));
+
+ phy_power_off(priv->analog);
+
+ return 0;
+}
+
+static int phy_meson_axg_mipi_dphy_exit(struct phy *phy)
+{
+ struct phy_meson_axg_mipi_dphy_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ ret = phy_exit(priv->analog);
+ if (ret)
+ return ret;
+
+ return reset_control_reset(priv->reset);
+}
+
+static const struct phy_ops phy_meson_axg_mipi_dphy_ops = {
+ .configure = phy_meson_axg_mipi_dphy_configure,
+ .init = phy_meson_axg_mipi_dphy_init,
+ .exit = phy_meson_axg_mipi_dphy_exit,
+ .power_on = phy_meson_axg_mipi_dphy_power_on,
+ .power_off = phy_meson_axg_mipi_dphy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int phy_meson_axg_mipi_dphy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct phy_provider *phy_provider;
+ struct phy_meson_axg_mipi_dphy_priv *priv;
+ struct phy *phy;
+ void __iomem *base;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ platform_set_drvdata(pdev, priv);
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ priv->regmap = devm_regmap_init_mmio(dev, base,
+ &phy_meson_axg_mipi_dphy_regmap_conf);
+ if (IS_ERR(priv->regmap))
+ return PTR_ERR(priv->regmap);
+
+ priv->clk = devm_clk_get(dev, "pclk");
+ if (IS_ERR(priv->clk))
+ return PTR_ERR(priv->clk);
+
+ priv->reset = devm_reset_control_get(dev, "phy");
+ if (IS_ERR(priv->reset))
+ return PTR_ERR(priv->reset);
+
+ priv->analog = devm_phy_get(dev, "analog");
+ if (IS_ERR(priv->analog))
+ return PTR_ERR(priv->analog);
+
+ ret = clk_prepare_enable(priv->clk);
+ if (ret)
+ return ret;
+
+ ret = reset_control_deassert(priv->reset);
+ if (ret)
+ return ret;
+
+ phy = devm_phy_create(dev, NULL, &phy_meson_axg_mipi_dphy_ops);
+ if (IS_ERR(phy)) {
+ ret = PTR_ERR(phy);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to create PHY\n");
+
+ return ret;
+ }
+
+ phy_set_drvdata(phy, priv);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id phy_meson_axg_mipi_dphy_of_match[] = {
+ { .compatible = "amlogic,axg-mipi-dphy", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, phy_meson_axg_mipi_dphy_of_match);
+
+static struct platform_driver phy_meson_axg_mipi_dphy_driver = {
+ .probe = phy_meson_axg_mipi_dphy_probe,
+ .driver = {
+ .name = "phy-meson-axg-mipi-dphy",
+ .of_match_table = phy_meson_axg_mipi_dphy_of_match,
+ },
+};
+module_platform_driver(phy_meson_axg_mipi_dphy_driver);
+
+MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
+MODULE_DESCRIPTION("Meson AXG MIPI DPHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/amlogic/phy-meson-axg-mipi-pcie-analog.c b/drivers/phy/amlogic/phy-meson-axg-mipi-pcie-analog.c
new file mode 100644
index 0000000000..ae898f93f9
--- /dev/null
+++ b/drivers/phy/amlogic/phy-meson-axg-mipi-pcie-analog.c
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Amlogic AXG MIPI + PCIE analog PHY driver
+ *
+ * Copyright (C) 2019 Remi Pommarel <repk@triplefau.lt>
+ */
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+#include <linux/delay.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <dt-bindings/phy/phy.h>
+
+#define HHI_MIPI_CNTL0 0x00
+#define HHI_MIPI_CNTL0_COMMON_BLOCK GENMASK(31, 28)
+#define HHI_MIPI_CNTL0_ENABLE BIT(29)
+#define HHI_MIPI_CNTL0_BANDGAP BIT(26)
+#define HHI_MIPI_CNTL0_DIF_REF_CTL1 GENMASK(25, 16)
+#define HHI_MIPI_CNTL0_DIF_REF_CTL0 GENMASK(15, 0)
+
+#define HHI_MIPI_CNTL1 0x04
+#define HHI_MIPI_CNTL1_CH0_CML_PDR_EN BIT(12)
+#define HHI_MIPI_CNTL1_LP_ABILITY GENMASK(5, 4)
+#define HHI_MIPI_CNTL1_LP_RESISTER BIT(3)
+#define HHI_MIPI_CNTL1_INPUT_SETTING BIT(2)
+#define HHI_MIPI_CNTL1_INPUT_SEL BIT(1)
+#define HHI_MIPI_CNTL1_PRBS7_EN BIT(0)
+
+#define HHI_MIPI_CNTL2 0x08
+#define HHI_MIPI_CNTL2_CH_PU GENMASK(31, 25)
+#define HHI_MIPI_CNTL2_CH_CTL GENMASK(24, 19)
+#define HHI_MIPI_CNTL2_CH0_DIGDR_EN BIT(18)
+#define HHI_MIPI_CNTL2_CH_DIGDR_EN BIT(17)
+#define HHI_MIPI_CNTL2_LPULPS_EN BIT(16)
+#define HHI_MIPI_CNTL2_CH_EN GENMASK(15, 11)
+#define HHI_MIPI_CNTL2_CH0_LP_CTL GENMASK(10, 1)
+
+#define DSI_LANE_0 BIT(4)
+#define DSI_LANE_1 BIT(3)
+#define DSI_LANE_CLK BIT(2)
+#define DSI_LANE_2 BIT(1)
+#define DSI_LANE_3 BIT(0)
+
+struct phy_axg_mipi_pcie_analog_priv {
+ struct phy *phy;
+ struct regmap *regmap;
+ bool dsi_configured;
+ bool dsi_enabled;
+ bool powered;
+ struct phy_configure_opts_mipi_dphy config;
+};
+
+static void phy_bandgap_enable(struct phy_axg_mipi_pcie_analog_priv *priv)
+{
+ regmap_update_bits(priv->regmap, HHI_MIPI_CNTL0,
+ HHI_MIPI_CNTL0_BANDGAP, HHI_MIPI_CNTL0_BANDGAP);
+
+ regmap_update_bits(priv->regmap, HHI_MIPI_CNTL0,
+ HHI_MIPI_CNTL0_ENABLE, HHI_MIPI_CNTL0_ENABLE);
+}
+
+static void phy_bandgap_disable(struct phy_axg_mipi_pcie_analog_priv *priv)
+{
+ regmap_update_bits(priv->regmap, HHI_MIPI_CNTL0,
+ HHI_MIPI_CNTL0_BANDGAP, 0);
+ regmap_update_bits(priv->regmap, HHI_MIPI_CNTL0,
+ HHI_MIPI_CNTL0_ENABLE, 0);
+}
+
+static void phy_dsi_analog_enable(struct phy_axg_mipi_pcie_analog_priv *priv)
+{
+ u32 reg;
+
+ regmap_update_bits(priv->regmap, HHI_MIPI_CNTL0,
+ HHI_MIPI_CNTL0_DIF_REF_CTL1,
+ FIELD_PREP(HHI_MIPI_CNTL0_DIF_REF_CTL1, 0x1b8));
+ regmap_update_bits(priv->regmap, HHI_MIPI_CNTL0,
+ BIT(31), BIT(31));
+ regmap_update_bits(priv->regmap, HHI_MIPI_CNTL0,
+ HHI_MIPI_CNTL0_DIF_REF_CTL0,
+ FIELD_PREP(HHI_MIPI_CNTL0_DIF_REF_CTL0, 0x8));
+
+ regmap_write(priv->regmap, HHI_MIPI_CNTL1, 0x001e);
+
+ regmap_write(priv->regmap, HHI_MIPI_CNTL2,
+ (0x26e0 << 16) | (0x459 << 0));
+
+ reg = DSI_LANE_CLK;
+ switch (priv->config.lanes) {
+ case 4:
+ reg |= DSI_LANE_3;
+ fallthrough;
+ case 3:
+ reg |= DSI_LANE_2;
+ fallthrough;
+ case 2:
+ reg |= DSI_LANE_1;
+ fallthrough;
+ case 1:
+ reg |= DSI_LANE_0;
+ break;
+ default:
+ reg = 0;
+ }
+
+ regmap_update_bits(priv->regmap, HHI_MIPI_CNTL2,
+ HHI_MIPI_CNTL2_CH_EN,
+ FIELD_PREP(HHI_MIPI_CNTL2_CH_EN, reg));
+
+ priv->dsi_enabled = true;
+}
+
+static void phy_dsi_analog_disable(struct phy_axg_mipi_pcie_analog_priv *priv)
+{
+ regmap_update_bits(priv->regmap, HHI_MIPI_CNTL0,
+ HHI_MIPI_CNTL0_DIF_REF_CTL1,
+ FIELD_PREP(HHI_MIPI_CNTL0_DIF_REF_CTL1, 0));
+ regmap_update_bits(priv->regmap, HHI_MIPI_CNTL0, BIT(31), 0);
+ regmap_update_bits(priv->regmap, HHI_MIPI_CNTL0,
+ HHI_MIPI_CNTL0_DIF_REF_CTL1, 0);
+
+ regmap_write(priv->regmap, HHI_MIPI_CNTL1, 0x6);
+
+ regmap_write(priv->regmap, HHI_MIPI_CNTL2, 0x00200000);
+
+ priv->dsi_enabled = false;
+}
+
+static int phy_axg_mipi_pcie_analog_configure(struct phy *phy,
+ union phy_configure_opts *opts)
+{
+ struct phy_axg_mipi_pcie_analog_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ ret = phy_mipi_dphy_config_validate(&opts->mipi_dphy);
+ if (ret)
+ return ret;
+
+ memcpy(&priv->config, opts, sizeof(priv->config));
+
+ priv->dsi_configured = true;
+
+ /* If PHY was already powered on, setup the DSI analog part */
+ if (priv->powered) {
+ /* If reconfiguring, disable & reconfigure */
+ if (priv->dsi_enabled)
+ phy_dsi_analog_disable(priv);
+
+ usleep_range(100, 200);
+
+ phy_dsi_analog_enable(priv);
+ }
+
+ return 0;
+}
+
+static int phy_axg_mipi_pcie_analog_power_on(struct phy *phy)
+{
+ struct phy_axg_mipi_pcie_analog_priv *priv = phy_get_drvdata(phy);
+
+ phy_bandgap_enable(priv);
+
+ if (priv->dsi_configured)
+ phy_dsi_analog_enable(priv);
+
+ priv->powered = true;
+
+ return 0;
+}
+
+static int phy_axg_mipi_pcie_analog_power_off(struct phy *phy)
+{
+ struct phy_axg_mipi_pcie_analog_priv *priv = phy_get_drvdata(phy);
+
+ phy_bandgap_disable(priv);
+
+ if (priv->dsi_enabled)
+ phy_dsi_analog_disable(priv);
+
+ priv->powered = false;
+
+ return 0;
+}
+
+static const struct phy_ops phy_axg_mipi_pcie_analog_ops = {
+ .configure = phy_axg_mipi_pcie_analog_configure,
+ .power_on = phy_axg_mipi_pcie_analog_power_on,
+ .power_off = phy_axg_mipi_pcie_analog_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int phy_axg_mipi_pcie_analog_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy;
+ struct device *dev = &pdev->dev;
+ struct phy_axg_mipi_pcie_analog_priv *priv;
+ struct device_node *np = dev->of_node, *parent_np;
+ struct regmap *map;
+ int ret;
+
+ priv = devm_kmalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ /* Get the hhi system controller node */
+ parent_np = of_get_parent(dev->of_node);
+ map = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(map)) {
+ dev_err(dev,
+ "failed to get HHI regmap\n");
+ return PTR_ERR(map);
+ }
+
+ priv->regmap = map;
+
+ priv->phy = devm_phy_create(dev, np, &phy_axg_mipi_pcie_analog_ops);
+ if (IS_ERR(priv->phy)) {
+ ret = PTR_ERR(priv->phy);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to create PHY\n");
+ return ret;
+ }
+
+ phy_set_drvdata(priv->phy, priv);
+ dev_set_drvdata(dev, priv);
+
+ phy = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy);
+}
+
+static const struct of_device_id phy_axg_mipi_pcie_analog_of_match[] = {
+ {
+ .compatible = "amlogic,axg-mipi-pcie-analog-phy",
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, phy_axg_mipi_pcie_analog_of_match);
+
+static struct platform_driver phy_axg_mipi_pcie_analog_driver = {
+ .probe = phy_axg_mipi_pcie_analog_probe,
+ .driver = {
+ .name = "phy-axg-mipi-pcie-analog",
+ .of_match_table = phy_axg_mipi_pcie_analog_of_match,
+ },
+};
+module_platform_driver(phy_axg_mipi_pcie_analog_driver);
+
+MODULE_AUTHOR("Remi Pommarel <repk@triplefau.lt>");
+MODULE_DESCRIPTION("Amlogic AXG MIPI + PCIE analog PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/amlogic/phy-meson-axg-pcie.c b/drivers/phy/amlogic/phy-meson-axg-pcie.c
new file mode 100644
index 0000000000..60be5cdc60
--- /dev/null
+++ b/drivers/phy/amlogic/phy-meson-axg-pcie.c
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Amlogic AXG PCIE PHY driver
+ *
+ * Copyright (C) 2020 Remi Pommarel <repk@triplefau.lt>
+ */
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/platform_device.h>
+#include <linux/bitfield.h>
+#include <dt-bindings/phy/phy.h>
+
+#define MESON_PCIE_REG0 0x00
+#define MESON_PCIE_COMMON_CLK BIT(4)
+#define MESON_PCIE_PORT_SEL GENMASK(3, 2)
+#define MESON_PCIE_CLK BIT(1)
+#define MESON_PCIE_POWERDOWN BIT(0)
+
+#define MESON_PCIE_TWO_X1 FIELD_PREP(MESON_PCIE_PORT_SEL, 0x3)
+#define MESON_PCIE_COMMON_REF_CLK FIELD_PREP(MESON_PCIE_COMMON_CLK, 0x1)
+#define MESON_PCIE_PHY_INIT (MESON_PCIE_TWO_X1 | \
+ MESON_PCIE_COMMON_REF_CLK)
+#define MESON_PCIE_RESET_DELAY 500
+
+struct phy_axg_pcie_priv {
+ struct phy *phy;
+ struct phy *analog;
+ struct regmap *regmap;
+ struct reset_control *reset;
+};
+
+static const struct regmap_config phy_axg_pcie_regmap_conf = {
+ .reg_bits = 8,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = MESON_PCIE_REG0,
+};
+
+static int phy_axg_pcie_power_on(struct phy *phy)
+{
+ struct phy_axg_pcie_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ ret = phy_power_on(priv->analog);
+ if (ret != 0)
+ return ret;
+
+ regmap_update_bits(priv->regmap, MESON_PCIE_REG0,
+ MESON_PCIE_POWERDOWN, 0);
+ return 0;
+}
+
+static int phy_axg_pcie_power_off(struct phy *phy)
+{
+ struct phy_axg_pcie_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ ret = phy_power_off(priv->analog);
+ if (ret != 0)
+ return ret;
+
+ regmap_update_bits(priv->regmap, MESON_PCIE_REG0,
+ MESON_PCIE_POWERDOWN, 1);
+ return 0;
+}
+
+static int phy_axg_pcie_init(struct phy *phy)
+{
+ struct phy_axg_pcie_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ ret = phy_init(priv->analog);
+ if (ret != 0)
+ return ret;
+
+ regmap_write(priv->regmap, MESON_PCIE_REG0, MESON_PCIE_PHY_INIT);
+ return reset_control_reset(priv->reset);
+}
+
+static int phy_axg_pcie_exit(struct phy *phy)
+{
+ struct phy_axg_pcie_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ ret = phy_exit(priv->analog);
+ if (ret != 0)
+ return ret;
+
+ return reset_control_reset(priv->reset);
+}
+
+static int phy_axg_pcie_reset(struct phy *phy)
+{
+ struct phy_axg_pcie_priv *priv = phy_get_drvdata(phy);
+ int ret = 0;
+
+ ret = phy_reset(priv->analog);
+ if (ret != 0)
+ goto out;
+
+ ret = reset_control_assert(priv->reset);
+ if (ret != 0)
+ goto out;
+ udelay(MESON_PCIE_RESET_DELAY);
+
+ ret = reset_control_deassert(priv->reset);
+ if (ret != 0)
+ goto out;
+ udelay(MESON_PCIE_RESET_DELAY);
+
+out:
+ return ret;
+}
+
+static const struct phy_ops phy_axg_pcie_ops = {
+ .init = phy_axg_pcie_init,
+ .exit = phy_axg_pcie_exit,
+ .power_on = phy_axg_pcie_power_on,
+ .power_off = phy_axg_pcie_power_off,
+ .reset = phy_axg_pcie_reset,
+ .owner = THIS_MODULE,
+};
+
+static int phy_axg_pcie_probe(struct platform_device *pdev)
+{
+ struct phy_provider *pphy;
+ struct device *dev = &pdev->dev;
+ struct phy_axg_pcie_priv *priv;
+ struct device_node *np = dev->of_node;
+ void __iomem *base;
+ int ret;
+
+ priv = devm_kmalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->phy = devm_phy_create(dev, np, &phy_axg_pcie_ops);
+ if (IS_ERR(priv->phy)) {
+ ret = PTR_ERR(priv->phy);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to create PHY\n");
+ return ret;
+ }
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ priv->regmap = devm_regmap_init_mmio(dev, base,
+ &phy_axg_pcie_regmap_conf);
+ if (IS_ERR(priv->regmap))
+ return PTR_ERR(priv->regmap);
+
+ priv->reset = devm_reset_control_array_get_exclusive(dev);
+ if (IS_ERR(priv->reset))
+ return PTR_ERR(priv->reset);
+
+ priv->analog = devm_phy_get(dev, "analog");
+ if (IS_ERR(priv->analog))
+ return PTR_ERR(priv->analog);
+
+ phy_set_drvdata(priv->phy, priv);
+ dev_set_drvdata(dev, priv);
+ pphy = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(pphy);
+}
+
+static const struct of_device_id phy_axg_pcie_of_match[] = {
+ {
+ .compatible = "amlogic,axg-pcie-phy",
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, phy_axg_pcie_of_match);
+
+static struct platform_driver phy_axg_pcie_driver = {
+ .probe = phy_axg_pcie_probe,
+ .driver = {
+ .name = "phy-axg-pcie",
+ .of_match_table = phy_axg_pcie_of_match,
+ },
+};
+module_platform_driver(phy_axg_pcie_driver);
+
+MODULE_AUTHOR("Remi Pommarel <repk@triplefau.lt>");
+MODULE_DESCRIPTION("Amlogic AXG PCIE PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/amlogic/phy-meson-g12a-mipi-dphy-analog.c b/drivers/phy/amlogic/phy-meson-g12a-mipi-dphy-analog.c
new file mode 100644
index 0000000000..46e5f7e7eb
--- /dev/null
+++ b/drivers/phy/amlogic/phy-meson-g12a-mipi-dphy-analog.c
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Meson G12A MIPI DSI Analog PHY
+ *
+ * Copyright (C) 2018 Amlogic, Inc. All rights reserved
+ * Copyright (C) 2022 BayLibre, SAS
+ * Author: Neil Armstrong <narmstrong@baylibre.com>
+ */
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+#include <linux/delay.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <dt-bindings/phy/phy.h>
+
+#define HHI_MIPI_CNTL0 0x00
+#define HHI_MIPI_CNTL0_DIF_REF_CTL1 GENMASK(31, 16)
+#define HHI_MIPI_CNTL0_DIF_REF_CTL0 GENMASK(15, 0)
+
+#define HHI_MIPI_CNTL1 0x04
+#define HHI_MIPI_CNTL1_BANDGAP BIT(16)
+#define HHI_MIPI_CNTL2_DIF_REF_CTL2 GENMASK(15, 0)
+
+#define HHI_MIPI_CNTL2 0x08
+#define HHI_MIPI_CNTL2_DIF_TX_CTL1 GENMASK(31, 16)
+#define HHI_MIPI_CNTL2_CH_EN GENMASK(15, 11)
+#define HHI_MIPI_CNTL2_DIF_TX_CTL0 GENMASK(10, 0)
+
+#define DSI_LANE_0 BIT(4)
+#define DSI_LANE_1 BIT(3)
+#define DSI_LANE_CLK BIT(2)
+#define DSI_LANE_2 BIT(1)
+#define DSI_LANE_3 BIT(0)
+
+struct phy_g12a_mipi_dphy_analog_priv {
+ struct phy *phy;
+ struct regmap *regmap;
+ struct phy_configure_opts_mipi_dphy config;
+};
+
+static int phy_g12a_mipi_dphy_analog_configure(struct phy *phy,
+ union phy_configure_opts *opts)
+{
+ struct phy_g12a_mipi_dphy_analog_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ ret = phy_mipi_dphy_config_validate(&opts->mipi_dphy);
+ if (ret)
+ return ret;
+
+ memcpy(&priv->config, opts, sizeof(priv->config));
+
+ return 0;
+}
+
+static int phy_g12a_mipi_dphy_analog_power_on(struct phy *phy)
+{
+ struct phy_g12a_mipi_dphy_analog_priv *priv = phy_get_drvdata(phy);
+ unsigned int reg;
+
+ regmap_write(priv->regmap, HHI_MIPI_CNTL0,
+ FIELD_PREP(HHI_MIPI_CNTL0_DIF_REF_CTL0, 0x8) |
+ FIELD_PREP(HHI_MIPI_CNTL0_DIF_REF_CTL1, 0xa487));
+
+ regmap_write(priv->regmap, HHI_MIPI_CNTL1,
+ FIELD_PREP(HHI_MIPI_CNTL2_DIF_REF_CTL2, 0x2e) |
+ HHI_MIPI_CNTL1_BANDGAP);
+
+ regmap_write(priv->regmap, HHI_MIPI_CNTL2,
+ FIELD_PREP(HHI_MIPI_CNTL2_DIF_TX_CTL0, 0x45a) |
+ FIELD_PREP(HHI_MIPI_CNTL2_DIF_TX_CTL1, 0x2680));
+
+ reg = DSI_LANE_CLK;
+ switch (priv->config.lanes) {
+ case 4:
+ reg |= DSI_LANE_3;
+ fallthrough;
+ case 3:
+ reg |= DSI_LANE_2;
+ fallthrough;
+ case 2:
+ reg |= DSI_LANE_1;
+ fallthrough;
+ case 1:
+ reg |= DSI_LANE_0;
+ break;
+ default:
+ reg = 0;
+ }
+
+ regmap_update_bits(priv->regmap, HHI_MIPI_CNTL2,
+ HHI_MIPI_CNTL2_CH_EN,
+ FIELD_PREP(HHI_MIPI_CNTL2_CH_EN, reg));
+
+ return 0;
+}
+
+static int phy_g12a_mipi_dphy_analog_power_off(struct phy *phy)
+{
+ struct phy_g12a_mipi_dphy_analog_priv *priv = phy_get_drvdata(phy);
+
+ regmap_write(priv->regmap, HHI_MIPI_CNTL0, 0);
+ regmap_write(priv->regmap, HHI_MIPI_CNTL1, 0);
+ regmap_write(priv->regmap, HHI_MIPI_CNTL2, 0);
+
+ return 0;
+}
+
+static const struct phy_ops phy_g12a_mipi_dphy_analog_ops = {
+ .configure = phy_g12a_mipi_dphy_analog_configure,
+ .power_on = phy_g12a_mipi_dphy_analog_power_on,
+ .power_off = phy_g12a_mipi_dphy_analog_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int phy_g12a_mipi_dphy_analog_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy;
+ struct device *dev = &pdev->dev;
+ struct phy_g12a_mipi_dphy_analog_priv *priv;
+ struct device_node *np = dev->of_node, *parent_np;
+ struct regmap *map;
+
+ priv = devm_kmalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ /* Get the hhi system controller node */
+ parent_np = of_get_parent(np);
+ map = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(map))
+ return dev_err_probe(dev, PTR_ERR(map), "failed to get HHI regmap\n");
+
+ priv->regmap = map;
+
+ priv->phy = devm_phy_create(dev, np, &phy_g12a_mipi_dphy_analog_ops);
+ if (IS_ERR(priv->phy))
+ return dev_err_probe(dev, PTR_ERR(priv->phy), "failed to create PHY\n");
+
+ phy_set_drvdata(priv->phy, priv);
+ dev_set_drvdata(dev, priv);
+
+ phy = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy);
+}
+
+static const struct of_device_id phy_g12a_mipi_dphy_analog_of_match[] = {
+ {
+ .compatible = "amlogic,g12a-mipi-dphy-analog",
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, phy_g12a_mipi_dphy_analog_of_match);
+
+static struct platform_driver phy_g12a_mipi_dphy_analog_driver = {
+ .probe = phy_g12a_mipi_dphy_analog_probe,
+ .driver = {
+ .name = "phy-meson-g12a-mipi-dphy-analog",
+ .of_match_table = phy_g12a_mipi_dphy_analog_of_match,
+ },
+};
+module_platform_driver(phy_g12a_mipi_dphy_analog_driver);
+
+MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
+MODULE_DESCRIPTION("Meson G12A MIPI Analog D-PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/amlogic/phy-meson-g12a-usb2.c b/drivers/phy/amlogic/phy-meson-g12a-usb2.c
new file mode 100644
index 0000000000..0e0b5c00b6
--- /dev/null
+++ b/drivers/phy/amlogic/phy-meson-g12a-usb2.c
@@ -0,0 +1,383 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Meson G12A USB2 PHY driver
+ *
+ * Copyright (C) 2017 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ * Copyright (C) 2017 Amlogic, Inc. All rights reserved
+ * Copyright (C) 2019 BayLibre, SAS
+ * Author: Neil Armstrong <narmstrong@baylibre.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+#define PHY_CTRL_R0 0x0
+#define PHY_CTRL_R1 0x4
+#define PHY_CTRL_R2 0x8
+#define PHY_CTRL_R3 0xc
+ #define PHY_CTRL_R3_SQUELCH_REF GENMASK(1, 0)
+ #define PHY_CTRL_R3_HSDIC_REF GENMASK(3, 2)
+ #define PHY_CTRL_R3_DISC_THRESH GENMASK(7, 4)
+
+#define PHY_CTRL_R4 0x10
+ #define PHY_CTRL_R4_CALIB_CODE_7_0 GENMASK(7, 0)
+ #define PHY_CTRL_R4_CALIB_CODE_15_8 GENMASK(15, 8)
+ #define PHY_CTRL_R4_CALIB_CODE_23_16 GENMASK(23, 16)
+ #define PHY_CTRL_R4_I_C2L_CAL_EN BIT(24)
+ #define PHY_CTRL_R4_I_C2L_CAL_RESET_N BIT(25)
+ #define PHY_CTRL_R4_I_C2L_CAL_DONE BIT(26)
+ #define PHY_CTRL_R4_TEST_BYPASS_MODE_EN BIT(27)
+ #define PHY_CTRL_R4_I_C2L_BIAS_TRIM_1_0 GENMASK(29, 28)
+ #define PHY_CTRL_R4_I_C2L_BIAS_TRIM_3_2 GENMASK(31, 30)
+
+#define PHY_CTRL_R5 0x14
+#define PHY_CTRL_R6 0x18
+#define PHY_CTRL_R7 0x1c
+#define PHY_CTRL_R8 0x20
+#define PHY_CTRL_R9 0x24
+#define PHY_CTRL_R10 0x28
+#define PHY_CTRL_R11 0x2c
+#define PHY_CTRL_R12 0x30
+#define PHY_CTRL_R13 0x34
+ #define PHY_CTRL_R13_CUSTOM_PATTERN_19 GENMASK(7, 0)
+ #define PHY_CTRL_R13_LOAD_STAT BIT(14)
+ #define PHY_CTRL_R13_UPDATE_PMA_SIGNALS BIT(15)
+ #define PHY_CTRL_R13_MIN_COUNT_FOR_SYNC_DET GENMASK(20, 16)
+ #define PHY_CTRL_R13_CLEAR_HOLD_HS_DISCONNECT BIT(21)
+ #define PHY_CTRL_R13_BYPASS_HOST_DISCONNECT_VAL BIT(22)
+ #define PHY_CTRL_R13_BYPASS_HOST_DISCONNECT_EN BIT(23)
+ #define PHY_CTRL_R13_I_C2L_HS_EN BIT(24)
+ #define PHY_CTRL_R13_I_C2L_FS_EN BIT(25)
+ #define PHY_CTRL_R13_I_C2L_LS_EN BIT(26)
+ #define PHY_CTRL_R13_I_C2L_HS_OE BIT(27)
+ #define PHY_CTRL_R13_I_C2L_FS_OE BIT(28)
+ #define PHY_CTRL_R13_I_C2L_HS_RX_EN BIT(29)
+ #define PHY_CTRL_R13_I_C2L_FSLS_RX_EN BIT(30)
+
+#define PHY_CTRL_R14 0x38
+ #define PHY_CTRL_R14_I_RDP_EN BIT(0)
+ #define PHY_CTRL_R14_I_RPU_SW1_EN BIT(1)
+ #define PHY_CTRL_R14_I_RPU_SW2_EN GENMASK(3, 2)
+ #define PHY_CTRL_R14_PG_RSTN BIT(4)
+ #define PHY_CTRL_R14_I_C2L_DATA_16_8 BIT(5)
+ #define PHY_CTRL_R14_I_C2L_ASSERT_SINGLE_EN_ZERO BIT(6)
+ #define PHY_CTRL_R14_BYPASS_CTRL_7_0 GENMASK(15, 8)
+ #define PHY_CTRL_R14_BYPASS_CTRL_15_8 GENMASK(23, 16)
+
+#define PHY_CTRL_R15 0x3c
+#define PHY_CTRL_R16 0x40
+ #define PHY_CTRL_R16_MPLL_M GENMASK(8, 0)
+ #define PHY_CTRL_R16_MPLL_N GENMASK(14, 10)
+ #define PHY_CTRL_R16_MPLL_TDC_MODE BIT(20)
+ #define PHY_CTRL_R16_MPLL_SDM_EN BIT(21)
+ #define PHY_CTRL_R16_MPLL_LOAD BIT(22)
+ #define PHY_CTRL_R16_MPLL_DCO_SDM_EN BIT(23)
+ #define PHY_CTRL_R16_MPLL_LOCK_LONG GENMASK(25, 24)
+ #define PHY_CTRL_R16_MPLL_LOCK_F BIT(26)
+ #define PHY_CTRL_R16_MPLL_FAST_LOCK BIT(27)
+ #define PHY_CTRL_R16_MPLL_EN BIT(28)
+ #define PHY_CTRL_R16_MPLL_RESET BIT(29)
+ #define PHY_CTRL_R16_MPLL_LOCK BIT(30)
+ #define PHY_CTRL_R16_MPLL_LOCK_DIG BIT(31)
+
+#define PHY_CTRL_R17 0x44
+ #define PHY_CTRL_R17_MPLL_FRAC_IN GENMASK(13, 0)
+ #define PHY_CTRL_R17_MPLL_FIX_EN BIT(16)
+ #define PHY_CTRL_R17_MPLL_LAMBDA1 GENMASK(19, 17)
+ #define PHY_CTRL_R17_MPLL_LAMBDA0 GENMASK(22, 20)
+ #define PHY_CTRL_R17_MPLL_FILTER_MODE BIT(23)
+ #define PHY_CTRL_R17_MPLL_FILTER_PVT2 GENMASK(27, 24)
+ #define PHY_CTRL_R17_MPLL_FILTER_PVT1 GENMASK(31, 28)
+
+#define PHY_CTRL_R18 0x48
+ #define PHY_CTRL_R18_MPLL_LKW_SEL GENMASK(1, 0)
+ #define PHY_CTRL_R18_MPLL_LK_W GENMASK(5, 2)
+ #define PHY_CTRL_R18_MPLL_LK_S GENMASK(11, 6)
+ #define PHY_CTRL_R18_MPLL_DCO_M_EN BIT(12)
+ #define PHY_CTRL_R18_MPLL_DCO_CLK_SEL BIT(13)
+ #define PHY_CTRL_R18_MPLL_PFD_GAIN GENMASK(15, 14)
+ #define PHY_CTRL_R18_MPLL_ROU GENMASK(18, 16)
+ #define PHY_CTRL_R18_MPLL_DATA_SEL GENMASK(21, 19)
+ #define PHY_CTRL_R18_MPLL_BIAS_ADJ GENMASK(23, 22)
+ #define PHY_CTRL_R18_MPLL_BB_MODE GENMASK(25, 24)
+ #define PHY_CTRL_R18_MPLL_ALPHA GENMASK(28, 26)
+ #define PHY_CTRL_R18_MPLL_ADJ_LDO GENMASK(30, 29)
+ #define PHY_CTRL_R18_MPLL_ACG_RANGE BIT(31)
+
+#define PHY_CTRL_R19 0x4c
+#define PHY_CTRL_R20 0x50
+ #define PHY_CTRL_R20_USB2_IDDET_EN BIT(0)
+ #define PHY_CTRL_R20_USB2_OTG_VBUS_TRIM_2_0 GENMASK(3, 1)
+ #define PHY_CTRL_R20_USB2_OTG_VBUSDET_EN BIT(4)
+ #define PHY_CTRL_R20_USB2_AMON_EN BIT(5)
+ #define PHY_CTRL_R20_USB2_CAL_CODE_R5 BIT(6)
+ #define PHY_CTRL_R20_BYPASS_OTG_DET BIT(7)
+ #define PHY_CTRL_R20_USB2_DMON_EN BIT(8)
+ #define PHY_CTRL_R20_USB2_DMON_SEL_3_0 GENMASK(12, 9)
+ #define PHY_CTRL_R20_USB2_EDGE_DRV_EN BIT(13)
+ #define PHY_CTRL_R20_USB2_EDGE_DRV_TRIM_1_0 GENMASK(15, 14)
+ #define PHY_CTRL_R20_USB2_BGR_ADJ_4_0 GENMASK(20, 16)
+ #define PHY_CTRL_R20_USB2_BGR_START BIT(21)
+ #define PHY_CTRL_R20_USB2_BGR_VREF_4_0 GENMASK(28, 24)
+ #define PHY_CTRL_R20_USB2_BGR_DBG_1_0 GENMASK(30, 29)
+ #define PHY_CTRL_R20_BYPASS_CAL_DONE_R5 BIT(31)
+
+#define PHY_CTRL_R21 0x54
+ #define PHY_CTRL_R21_USB2_BGR_FORCE BIT(0)
+ #define PHY_CTRL_R21_USB2_CAL_ACK_EN BIT(1)
+ #define PHY_CTRL_R21_USB2_OTG_ACA_EN BIT(2)
+ #define PHY_CTRL_R21_USB2_TX_STRG_PD BIT(3)
+ #define PHY_CTRL_R21_USB2_OTG_ACA_TRIM_1_0 GENMASK(5, 4)
+ #define PHY_CTRL_R21_BYPASS_UTMI_CNTR GENMASK(15, 6)
+ #define PHY_CTRL_R21_BYPASS_UTMI_REG GENMASK(25, 20)
+
+#define PHY_CTRL_R22 0x58
+#define PHY_CTRL_R23 0x5c
+
+#define RESET_COMPLETE_TIME 1000
+#define PLL_RESET_COMPLETE_TIME 100
+
+enum meson_soc_id {
+ MESON_SOC_G12A = 0,
+ MESON_SOC_A1,
+};
+
+struct phy_meson_g12a_usb2_priv {
+ struct device *dev;
+ struct regmap *regmap;
+ struct clk *clk;
+ struct reset_control *reset;
+ int soc_id;
+};
+
+static const struct regmap_config phy_meson_g12a_usb2_regmap_conf = {
+ .reg_bits = 8,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = PHY_CTRL_R23,
+};
+
+static int phy_meson_g12a_usb2_init(struct phy *phy)
+{
+ struct phy_meson_g12a_usb2_priv *priv = phy_get_drvdata(phy);
+ int ret;
+ unsigned int value;
+
+ ret = clk_prepare_enable(priv->clk);
+ if (ret)
+ return ret;
+
+ ret = reset_control_reset(priv->reset);
+ if (ret) {
+ clk_disable_unprepare(priv->clk);
+ return ret;
+ }
+
+ udelay(RESET_COMPLETE_TIME);
+
+ /* usb2_otg_aca_en == 0 */
+ regmap_update_bits(priv->regmap, PHY_CTRL_R21,
+ PHY_CTRL_R21_USB2_OTG_ACA_EN, 0);
+
+ /* PLL Setup : 24MHz * 20 / 1 = 480MHz */
+ regmap_write(priv->regmap, PHY_CTRL_R16,
+ FIELD_PREP(PHY_CTRL_R16_MPLL_M, 20) |
+ FIELD_PREP(PHY_CTRL_R16_MPLL_N, 1) |
+ PHY_CTRL_R16_MPLL_LOAD |
+ FIELD_PREP(PHY_CTRL_R16_MPLL_LOCK_LONG, 1) |
+ PHY_CTRL_R16_MPLL_FAST_LOCK |
+ PHY_CTRL_R16_MPLL_EN |
+ PHY_CTRL_R16_MPLL_RESET);
+
+ regmap_write(priv->regmap, PHY_CTRL_R17,
+ FIELD_PREP(PHY_CTRL_R17_MPLL_FRAC_IN, 0) |
+ FIELD_PREP(PHY_CTRL_R17_MPLL_LAMBDA1, 7) |
+ FIELD_PREP(PHY_CTRL_R17_MPLL_LAMBDA0, 7) |
+ FIELD_PREP(PHY_CTRL_R17_MPLL_FILTER_PVT2, 2) |
+ FIELD_PREP(PHY_CTRL_R17_MPLL_FILTER_PVT1, 9));
+
+ value = FIELD_PREP(PHY_CTRL_R18_MPLL_LKW_SEL, 1) |
+ FIELD_PREP(PHY_CTRL_R18_MPLL_LK_W, 9) |
+ FIELD_PREP(PHY_CTRL_R18_MPLL_LK_S, 0x27) |
+ FIELD_PREP(PHY_CTRL_R18_MPLL_PFD_GAIN, 1) |
+ FIELD_PREP(PHY_CTRL_R18_MPLL_ROU, 7) |
+ FIELD_PREP(PHY_CTRL_R18_MPLL_DATA_SEL, 3) |
+ FIELD_PREP(PHY_CTRL_R18_MPLL_BIAS_ADJ, 1) |
+ FIELD_PREP(PHY_CTRL_R18_MPLL_BB_MODE, 0) |
+ FIELD_PREP(PHY_CTRL_R18_MPLL_ALPHA, 3) |
+ FIELD_PREP(PHY_CTRL_R18_MPLL_ADJ_LDO, 1) |
+ PHY_CTRL_R18_MPLL_ACG_RANGE;
+
+ if (priv->soc_id == MESON_SOC_A1)
+ value |= PHY_CTRL_R18_MPLL_DCO_CLK_SEL;
+
+ regmap_write(priv->regmap, PHY_CTRL_R18, value);
+
+ udelay(PLL_RESET_COMPLETE_TIME);
+
+ /* UnReset PLL */
+ regmap_write(priv->regmap, PHY_CTRL_R16,
+ FIELD_PREP(PHY_CTRL_R16_MPLL_M, 20) |
+ FIELD_PREP(PHY_CTRL_R16_MPLL_N, 1) |
+ PHY_CTRL_R16_MPLL_LOAD |
+ FIELD_PREP(PHY_CTRL_R16_MPLL_LOCK_LONG, 1) |
+ PHY_CTRL_R16_MPLL_FAST_LOCK |
+ PHY_CTRL_R16_MPLL_EN);
+
+ /* PHY Tuning */
+ regmap_write(priv->regmap, PHY_CTRL_R20,
+ FIELD_PREP(PHY_CTRL_R20_USB2_OTG_VBUS_TRIM_2_0, 4) |
+ PHY_CTRL_R20_USB2_OTG_VBUSDET_EN |
+ FIELD_PREP(PHY_CTRL_R20_USB2_DMON_SEL_3_0, 15) |
+ PHY_CTRL_R20_USB2_EDGE_DRV_EN |
+ FIELD_PREP(PHY_CTRL_R20_USB2_EDGE_DRV_TRIM_1_0, 3) |
+ FIELD_PREP(PHY_CTRL_R20_USB2_BGR_ADJ_4_0, 0) |
+ FIELD_PREP(PHY_CTRL_R20_USB2_BGR_VREF_4_0, 0) |
+ FIELD_PREP(PHY_CTRL_R20_USB2_BGR_DBG_1_0, 0));
+
+ if (priv->soc_id == MESON_SOC_G12A)
+ regmap_write(priv->regmap, PHY_CTRL_R4,
+ FIELD_PREP(PHY_CTRL_R4_CALIB_CODE_7_0, 0xf) |
+ FIELD_PREP(PHY_CTRL_R4_CALIB_CODE_15_8, 0xf) |
+ FIELD_PREP(PHY_CTRL_R4_CALIB_CODE_23_16, 0xf) |
+ PHY_CTRL_R4_TEST_BYPASS_MODE_EN |
+ FIELD_PREP(PHY_CTRL_R4_I_C2L_BIAS_TRIM_1_0, 0) |
+ FIELD_PREP(PHY_CTRL_R4_I_C2L_BIAS_TRIM_3_2, 0));
+ else if (priv->soc_id == MESON_SOC_A1) {
+ regmap_write(priv->regmap, PHY_CTRL_R21,
+ PHY_CTRL_R21_USB2_CAL_ACK_EN |
+ PHY_CTRL_R21_USB2_TX_STRG_PD |
+ FIELD_PREP(PHY_CTRL_R21_USB2_OTG_ACA_TRIM_1_0, 2));
+
+ /* Analog Settings */
+ regmap_write(priv->regmap, PHY_CTRL_R13,
+ FIELD_PREP(PHY_CTRL_R13_MIN_COUNT_FOR_SYNC_DET, 7));
+ }
+
+ /* Tuning Disconnect Threshold */
+ regmap_write(priv->regmap, PHY_CTRL_R3,
+ FIELD_PREP(PHY_CTRL_R3_SQUELCH_REF, 0) |
+ FIELD_PREP(PHY_CTRL_R3_HSDIC_REF, 1) |
+ FIELD_PREP(PHY_CTRL_R3_DISC_THRESH, 3));
+
+ if (priv->soc_id == MESON_SOC_G12A) {
+ /* Analog Settings */
+ regmap_write(priv->regmap, PHY_CTRL_R14, 0);
+ regmap_write(priv->regmap, PHY_CTRL_R13,
+ PHY_CTRL_R13_UPDATE_PMA_SIGNALS |
+ FIELD_PREP(PHY_CTRL_R13_MIN_COUNT_FOR_SYNC_DET, 7));
+ }
+
+ return 0;
+}
+
+static int phy_meson_g12a_usb2_exit(struct phy *phy)
+{
+ struct phy_meson_g12a_usb2_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ ret = reset_control_reset(priv->reset);
+ if (!ret)
+ clk_disable_unprepare(priv->clk);
+
+ return ret;
+}
+
+/* set_mode is not needed, mode setting is handled via the UTMI bus */
+static const struct phy_ops phy_meson_g12a_usb2_ops = {
+ .init = phy_meson_g12a_usb2_init,
+ .exit = phy_meson_g12a_usb2_exit,
+ .owner = THIS_MODULE,
+};
+
+static int phy_meson_g12a_usb2_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct phy_provider *phy_provider;
+ struct phy_meson_g12a_usb2_priv *priv;
+ struct phy *phy;
+ void __iomem *base;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ platform_set_drvdata(pdev, priv);
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ priv->soc_id = (uintptr_t)of_device_get_match_data(&pdev->dev);
+
+ priv->regmap = devm_regmap_init_mmio(dev, base,
+ &phy_meson_g12a_usb2_regmap_conf);
+ if (IS_ERR(priv->regmap))
+ return PTR_ERR(priv->regmap);
+
+ priv->clk = devm_clk_get(dev, "xtal");
+ if (IS_ERR(priv->clk))
+ return PTR_ERR(priv->clk);
+
+ priv->reset = devm_reset_control_get(dev, "phy");
+ if (IS_ERR(priv->reset))
+ return PTR_ERR(priv->reset);
+
+ ret = reset_control_deassert(priv->reset);
+ if (ret)
+ return ret;
+
+ phy = devm_phy_create(dev, NULL, &phy_meson_g12a_usb2_ops);
+ if (IS_ERR(phy)) {
+ ret = PTR_ERR(phy);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to create PHY\n");
+
+ return ret;
+ }
+
+ phy_set_bus_width(phy, 8);
+ phy_set_drvdata(phy, priv);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id phy_meson_g12a_usb2_of_match[] = {
+ {
+ .compatible = "amlogic,g12a-usb2-phy",
+ .data = (void *)MESON_SOC_G12A,
+ },
+ {
+ .compatible = "amlogic,a1-usb2-phy",
+ .data = (void *)MESON_SOC_A1,
+ },
+ { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, phy_meson_g12a_usb2_of_match);
+
+static struct platform_driver phy_meson_g12a_usb2_driver = {
+ .probe = phy_meson_g12a_usb2_probe,
+ .driver = {
+ .name = "phy-meson-g12a-usb2",
+ .of_match_table = phy_meson_g12a_usb2_of_match,
+ },
+};
+module_platform_driver(phy_meson_g12a_usb2_driver);
+
+MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
+MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
+MODULE_DESCRIPTION("Meson G12A USB2 PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/amlogic/phy-meson-g12a-usb3-pcie.c b/drivers/phy/amlogic/phy-meson-g12a-usb3-pcie.c
new file mode 100644
index 0000000000..2712c4bd54
--- /dev/null
+++ b/drivers/phy/amlogic/phy-meson-g12a-usb3-pcie.c
@@ -0,0 +1,447 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Amlogic G12A USB3 + PCIE Combo PHY driver
+ *
+ * Copyright (C) 2017 Amlogic, Inc. All rights reserved
+ * Copyright (C) 2019 BayLibre, SAS
+ * Author: Neil Armstrong <narmstrong@baylibre.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/platform_device.h>
+#include <dt-bindings/phy/phy.h>
+
+#define PHY_R0 0x00
+ #define PHY_R0_PCIE_POWER_STATE GENMASK(4, 0)
+ #define PHY_R0_PCIE_USB3_SWITCH GENMASK(6, 5)
+
+#define PHY_R1 0x04
+ #define PHY_R1_PHY_TX1_TERM_OFFSET GENMASK(4, 0)
+ #define PHY_R1_PHY_TX0_TERM_OFFSET GENMASK(9, 5)
+ #define PHY_R1_PHY_RX1_EQ GENMASK(12, 10)
+ #define PHY_R1_PHY_RX0_EQ GENMASK(15, 13)
+ #define PHY_R1_PHY_LOS_LEVEL GENMASK(20, 16)
+ #define PHY_R1_PHY_LOS_BIAS GENMASK(23, 21)
+ #define PHY_R1_PHY_REF_CLKDIV2 BIT(24)
+ #define PHY_R1_PHY_MPLL_MULTIPLIER GENMASK(31, 25)
+
+#define PHY_R2 0x08
+ #define PHY_R2_PCS_TX_DEEMPH_GEN2_6DB GENMASK(5, 0)
+ #define PHY_R2_PCS_TX_DEEMPH_GEN2_3P5DB GENMASK(11, 6)
+ #define PHY_R2_PCS_TX_DEEMPH_GEN1 GENMASK(17, 12)
+ #define PHY_R2_PHY_TX_VBOOST_LVL GENMASK(20, 18)
+
+#define PHY_R4 0x10
+ #define PHY_R4_PHY_CR_WRITE BIT(0)
+ #define PHY_R4_PHY_CR_READ BIT(1)
+ #define PHY_R4_PHY_CR_DATA_IN GENMASK(17, 2)
+ #define PHY_R4_PHY_CR_CAP_DATA BIT(18)
+ #define PHY_R4_PHY_CR_CAP_ADDR BIT(19)
+
+#define PHY_R5 0x14
+ #define PHY_R5_PHY_CR_DATA_OUT GENMASK(15, 0)
+ #define PHY_R5_PHY_CR_ACK BIT(16)
+ #define PHY_R5_PHY_BS_OUT BIT(17)
+
+#define PCIE_RESET_DELAY 500
+
+struct phy_g12a_usb3_pcie_priv {
+ struct regmap *regmap;
+ struct regmap *regmap_cr;
+ struct clk *clk_ref;
+ struct reset_control *reset;
+ struct phy *phy;
+ unsigned int mode;
+};
+
+static const struct regmap_config phy_g12a_usb3_pcie_regmap_conf = {
+ .reg_bits = 8,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = PHY_R5,
+};
+
+static int phy_g12a_usb3_pcie_cr_bus_addr(struct phy_g12a_usb3_pcie_priv *priv,
+ unsigned int addr)
+{
+ unsigned int val, reg;
+ int ret;
+
+ reg = FIELD_PREP(PHY_R4_PHY_CR_DATA_IN, addr);
+
+ regmap_write(priv->regmap, PHY_R4, reg);
+ regmap_write(priv->regmap, PHY_R4, reg);
+
+ regmap_write(priv->regmap, PHY_R4, reg | PHY_R4_PHY_CR_CAP_ADDR);
+
+ ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
+ (val & PHY_R5_PHY_CR_ACK),
+ 5, 1000);
+ if (ret)
+ return ret;
+
+ regmap_write(priv->regmap, PHY_R4, reg);
+
+ ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
+ !(val & PHY_R5_PHY_CR_ACK),
+ 5, 1000);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int phy_g12a_usb3_pcie_cr_bus_read(void *context, unsigned int addr,
+ unsigned int *data)
+{
+ struct phy_g12a_usb3_pcie_priv *priv = context;
+ unsigned int val;
+ int ret;
+
+ ret = phy_g12a_usb3_pcie_cr_bus_addr(priv, addr);
+ if (ret)
+ return ret;
+
+ regmap_write(priv->regmap, PHY_R4, 0);
+ regmap_write(priv->regmap, PHY_R4, PHY_R4_PHY_CR_READ);
+
+ ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
+ (val & PHY_R5_PHY_CR_ACK),
+ 5, 1000);
+ if (ret)
+ return ret;
+
+ *data = FIELD_GET(PHY_R5_PHY_CR_DATA_OUT, val);
+
+ regmap_write(priv->regmap, PHY_R4, 0);
+
+ ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
+ !(val & PHY_R5_PHY_CR_ACK),
+ 5, 1000);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int phy_g12a_usb3_pcie_cr_bus_write(void *context, unsigned int addr,
+ unsigned int data)
+{
+ struct phy_g12a_usb3_pcie_priv *priv = context;
+ unsigned int val, reg;
+ int ret;
+
+ ret = phy_g12a_usb3_pcie_cr_bus_addr(priv, addr);
+ if (ret)
+ return ret;
+
+ reg = FIELD_PREP(PHY_R4_PHY_CR_DATA_IN, data);
+
+ regmap_write(priv->regmap, PHY_R4, reg);
+ regmap_write(priv->regmap, PHY_R4, reg);
+
+ regmap_write(priv->regmap, PHY_R4, reg | PHY_R4_PHY_CR_CAP_DATA);
+
+ ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
+ (val & PHY_R5_PHY_CR_ACK),
+ 5, 1000);
+ if (ret)
+ return ret;
+
+ regmap_write(priv->regmap, PHY_R4, reg);
+
+ ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
+ (val & PHY_R5_PHY_CR_ACK) == 0,
+ 5, 1000);
+ if (ret)
+ return ret;
+
+ regmap_write(priv->regmap, PHY_R4, reg);
+
+ regmap_write(priv->regmap, PHY_R4, reg | PHY_R4_PHY_CR_WRITE);
+
+ ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
+ (val & PHY_R5_PHY_CR_ACK),
+ 5, 1000);
+ if (ret)
+ return ret;
+
+ regmap_write(priv->regmap, PHY_R4, reg);
+
+ ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val,
+ (val & PHY_R5_PHY_CR_ACK) == 0,
+ 5, 1000);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct regmap_config phy_g12a_usb3_pcie_cr_regmap_conf = {
+ .reg_bits = 16,
+ .val_bits = 16,
+ .reg_read = phy_g12a_usb3_pcie_cr_bus_read,
+ .reg_write = phy_g12a_usb3_pcie_cr_bus_write,
+ .max_register = 0xffff,
+ .disable_locking = true,
+};
+
+static int phy_g12a_usb3_init(struct phy *phy)
+{
+ struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy);
+ int data, ret;
+
+ ret = reset_control_reset(priv->reset);
+ if (ret)
+ return ret;
+
+ /* Switch PHY to USB3 */
+ /* TODO figure out how to handle when PCIe was set in the bootloader */
+ regmap_update_bits(priv->regmap, PHY_R0,
+ PHY_R0_PCIE_USB3_SWITCH,
+ PHY_R0_PCIE_USB3_SWITCH);
+
+ /*
+ * WORKAROUND: There is SSPHY suspend bug due to
+ * which USB enumerates
+ * in HS mode instead of SS mode. Workaround it by asserting
+ * LANE0.TX_ALT_BLOCK.EN_ALT_BUS to enable TX to use alt bus
+ * mode
+ */
+ ret = regmap_update_bits(priv->regmap_cr, 0x102d, BIT(7), BIT(7));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(priv->regmap_cr, 0x1010, 0xff0, 20);
+ if (ret)
+ return ret;
+
+ /*
+ * Fix RX Equalization setting as follows
+ * LANE0.RX_OVRD_IN_HI. RX_EQ_EN set to 0
+ * LANE0.RX_OVRD_IN_HI.RX_EQ_EN_OVRD set to 1
+ * LANE0.RX_OVRD_IN_HI.RX_EQ set to 3
+ * LANE0.RX_OVRD_IN_HI.RX_EQ_OVRD set to 1
+ */
+ ret = regmap_read(priv->regmap_cr, 0x1006, &data);
+ if (ret)
+ return ret;
+
+ data &= ~BIT(6);
+ data |= BIT(7);
+ data &= ~(0x7 << 8);
+ data |= (0x3 << 8);
+ data |= (1 << 11);
+ ret = regmap_write(priv->regmap_cr, 0x1006, data);
+ if (ret)
+ return ret;
+
+ /*
+ * Set EQ and TX launch amplitudes as follows
+ * LANE0.TX_OVRD_DRV_LO.PREEMPH set to 22
+ * LANE0.TX_OVRD_DRV_LO.AMPLITUDE set to 127
+ * LANE0.TX_OVRD_DRV_LO.EN set to 1.
+ */
+ ret = regmap_read(priv->regmap_cr, 0x1002, &data);
+ if (ret)
+ return ret;
+
+ data &= ~0x3f80;
+ data |= (0x16 << 7);
+ data &= ~0x7f;
+ data |= (0x7f | BIT(14));
+ ret = regmap_write(priv->regmap_cr, 0x1002, data);
+ if (ret)
+ return ret;
+
+ /* MPLL_LOOP_CTL.PROP_CNTRL = 8 */
+ ret = regmap_update_bits(priv->regmap_cr, 0x30, 0xf << 4, 8 << 4);
+ if (ret)
+ return ret;
+
+ regmap_update_bits(priv->regmap, PHY_R2,
+ PHY_R2_PHY_TX_VBOOST_LVL,
+ FIELD_PREP(PHY_R2_PHY_TX_VBOOST_LVL, 0x4));
+
+ regmap_update_bits(priv->regmap, PHY_R1,
+ PHY_R1_PHY_LOS_BIAS | PHY_R1_PHY_LOS_LEVEL,
+ FIELD_PREP(PHY_R1_PHY_LOS_BIAS, 4) |
+ FIELD_PREP(PHY_R1_PHY_LOS_LEVEL, 9));
+
+ return 0;
+}
+
+static int phy_g12a_usb3_pcie_power_on(struct phy *phy)
+{
+ struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy);
+
+ if (priv->mode == PHY_TYPE_USB3)
+ return 0;
+
+ regmap_update_bits(priv->regmap, PHY_R0,
+ PHY_R0_PCIE_POWER_STATE,
+ FIELD_PREP(PHY_R0_PCIE_POWER_STATE, 0x1c));
+
+ return 0;
+}
+
+static int phy_g12a_usb3_pcie_power_off(struct phy *phy)
+{
+ struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy);
+
+ if (priv->mode == PHY_TYPE_USB3)
+ return 0;
+
+ regmap_update_bits(priv->regmap, PHY_R0,
+ PHY_R0_PCIE_POWER_STATE,
+ FIELD_PREP(PHY_R0_PCIE_POWER_STATE, 0x1d));
+
+ return 0;
+}
+
+static int phy_g12a_usb3_pcie_reset(struct phy *phy)
+{
+ struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ if (priv->mode == PHY_TYPE_USB3)
+ return 0;
+
+ ret = reset_control_assert(priv->reset);
+ if (ret)
+ return ret;
+
+ udelay(PCIE_RESET_DELAY);
+
+ ret = reset_control_deassert(priv->reset);
+ if (ret)
+ return ret;
+
+ udelay(PCIE_RESET_DELAY);
+
+ return 0;
+}
+
+static int phy_g12a_usb3_pcie_init(struct phy *phy)
+{
+ struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy);
+
+ if (priv->mode == PHY_TYPE_USB3)
+ return phy_g12a_usb3_init(phy);
+
+ return 0;
+}
+
+static int phy_g12a_usb3_pcie_exit(struct phy *phy)
+{
+ struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy);
+
+ if (priv->mode == PHY_TYPE_USB3)
+ return reset_control_reset(priv->reset);
+
+ return 0;
+}
+
+static struct phy *phy_g12a_usb3_pcie_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct phy_g12a_usb3_pcie_priv *priv = dev_get_drvdata(dev);
+ unsigned int mode;
+
+ if (args->args_count < 1) {
+ dev_err(dev, "invalid number of arguments\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ mode = args->args[0];
+
+ if (mode != PHY_TYPE_USB3 && mode != PHY_TYPE_PCIE) {
+ dev_err(dev, "invalid phy mode select argument\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ priv->mode = mode;
+
+ return priv->phy;
+}
+
+static const struct phy_ops phy_g12a_usb3_pcie_ops = {
+ .init = phy_g12a_usb3_pcie_init,
+ .exit = phy_g12a_usb3_pcie_exit,
+ .power_on = phy_g12a_usb3_pcie_power_on,
+ .power_off = phy_g12a_usb3_pcie_power_off,
+ .reset = phy_g12a_usb3_pcie_reset,
+ .owner = THIS_MODULE,
+};
+
+static int phy_g12a_usb3_pcie_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct phy_g12a_usb3_pcie_priv *priv;
+ struct phy_provider *phy_provider;
+ void __iomem *base;
+
+ priv = devm_kzalloc(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->regmap = devm_regmap_init_mmio(dev, base,
+ &phy_g12a_usb3_pcie_regmap_conf);
+ if (IS_ERR(priv->regmap))
+ return PTR_ERR(priv->regmap);
+
+ priv->regmap_cr = devm_regmap_init(dev, NULL, priv,
+ &phy_g12a_usb3_pcie_cr_regmap_conf);
+ if (IS_ERR(priv->regmap_cr))
+ return PTR_ERR(priv->regmap_cr);
+
+ priv->clk_ref = devm_clk_get_enabled(dev, "ref_clk");
+ if (IS_ERR(priv->clk_ref))
+ return PTR_ERR(priv->clk_ref);
+
+ priv->reset = devm_reset_control_array_get_exclusive(dev);
+ if (IS_ERR(priv->reset))
+ return PTR_ERR(priv->reset);
+
+ priv->phy = devm_phy_create(dev, np, &phy_g12a_usb3_pcie_ops);
+ if (IS_ERR(priv->phy))
+ return dev_err_probe(dev, PTR_ERR(priv->phy), "failed to create PHY\n");
+
+ phy_set_drvdata(priv->phy, priv);
+ dev_set_drvdata(dev, priv);
+
+ phy_provider = devm_of_phy_provider_register(dev,
+ phy_g12a_usb3_pcie_xlate);
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id phy_g12a_usb3_pcie_of_match[] = {
+ { .compatible = "amlogic,g12a-usb3-pcie-phy", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, phy_g12a_usb3_pcie_of_match);
+
+static struct platform_driver phy_g12a_usb3_pcie_driver = {
+ .probe = phy_g12a_usb3_pcie_probe,
+ .driver = {
+ .name = "phy-g12a-usb3-pcie",
+ .of_match_table = phy_g12a_usb3_pcie_of_match,
+ },
+};
+module_platform_driver(phy_g12a_usb3_pcie_driver);
+
+MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
+MODULE_DESCRIPTION("Amlogic G12A USB3 + PCIE Combo PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/amlogic/phy-meson-gxl-usb2.c b/drivers/phy/amlogic/phy-meson-gxl-usb2.c
new file mode 100644
index 0000000000..14ea89927a
--- /dev/null
+++ b/drivers/phy/amlogic/phy-meson-gxl-usb2.c
@@ -0,0 +1,301 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Meson GXL and GXM USB2 PHY driver
+ *
+ * Copyright (C) 2017 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+/* bits [31:27] are read-only */
+#define U2P_R0 0x0
+ #define U2P_R0_BYPASS_SEL BIT(0)
+ #define U2P_R0_BYPASS_DM_EN BIT(1)
+ #define U2P_R0_BYPASS_DP_EN BIT(2)
+ #define U2P_R0_TXBITSTUFF_ENH BIT(3)
+ #define U2P_R0_TXBITSTUFF_EN BIT(4)
+ #define U2P_R0_DM_PULLDOWN BIT(5)
+ #define U2P_R0_DP_PULLDOWN BIT(6)
+ #define U2P_R0_DP_VBUS_VLD_EXT_SEL BIT(7)
+ #define U2P_R0_DP_VBUS_VLD_EXT BIT(8)
+ #define U2P_R0_ADP_PRB_EN BIT(9)
+ #define U2P_R0_ADP_DISCHARGE BIT(10)
+ #define U2P_R0_ADP_CHARGE BIT(11)
+ #define U2P_R0_DRV_VBUS BIT(12)
+ #define U2P_R0_ID_PULLUP BIT(13)
+ #define U2P_R0_LOOPBACK_EN_B BIT(14)
+ #define U2P_R0_OTG_DISABLE BIT(15)
+ #define U2P_R0_COMMON_ONN BIT(16)
+ #define U2P_R0_FSEL_MASK GENMASK(19, 17)
+ #define U2P_R0_REF_CLK_SEL_MASK GENMASK(21, 20)
+ #define U2P_R0_POWER_ON_RESET BIT(22)
+ #define U2P_R0_V_ATE_TEST_EN_B_MASK GENMASK(24, 23)
+ #define U2P_R0_ID_SET_ID_DQ BIT(25)
+ #define U2P_R0_ATE_RESET BIT(26)
+ #define U2P_R0_FSV_MINUS BIT(27)
+ #define U2P_R0_FSV_PLUS BIT(28)
+ #define U2P_R0_BYPASS_DM_DATA BIT(29)
+ #define U2P_R0_BYPASS_DP_DATA BIT(30)
+
+#define U2P_R1 0x4
+ #define U2P_R1_BURN_IN_TEST BIT(0)
+ #define U2P_R1_ACA_ENABLE BIT(1)
+ #define U2P_R1_DCD_ENABLE BIT(2)
+ #define U2P_R1_VDAT_SRC_EN_B BIT(3)
+ #define U2P_R1_VDAT_DET_EN_B BIT(4)
+ #define U2P_R1_CHARGES_SEL BIT(5)
+ #define U2P_R1_TX_PREEMP_PULSE_TUNE BIT(6)
+ #define U2P_R1_TX_PREEMP_AMP_TUNE_MASK GENMASK(8, 7)
+ #define U2P_R1_TX_RES_TUNE_MASK GENMASK(10, 9)
+ #define U2P_R1_TX_RISE_TUNE_MASK GENMASK(12, 11)
+ #define U2P_R1_TX_VREF_TUNE_MASK GENMASK(16, 13)
+ #define U2P_R1_TX_FSLS_TUNE_MASK GENMASK(20, 17)
+ #define U2P_R1_TX_HSXV_TUNE_MASK GENMASK(22, 21)
+ #define U2P_R1_OTG_TUNE_MASK GENMASK(25, 23)
+ #define U2P_R1_SQRX_TUNE_MASK GENMASK(28, 26)
+ #define U2P_R1_COMP_DIS_TUNE_MASK GENMASK(31, 29)
+
+/* bits [31:14] are read-only */
+#define U2P_R2 0x8
+ #define U2P_R2_TESTDATA_IN_MASK GENMASK(7, 0)
+ #define U2P_R2_TESTADDR_MASK GENMASK(11, 8)
+ #define U2P_R2_TESTDATA_OUT_SEL BIT(12)
+ #define U2P_R2_TESTCLK BIT(13)
+ #define U2P_R2_TESTDATA_OUT_MASK GENMASK(17, 14)
+ #define U2P_R2_ACA_PIN_RANGE_C BIT(18)
+ #define U2P_R2_ACA_PIN_RANGE_B BIT(19)
+ #define U2P_R2_ACA_PIN_RANGE_A BIT(20)
+ #define U2P_R2_ACA_PIN_GND BIT(21)
+ #define U2P_R2_ACA_PIN_FLOAT BIT(22)
+ #define U2P_R2_CHARGE_DETECT BIT(23)
+ #define U2P_R2_DEVICE_SESSION_VALID BIT(24)
+ #define U2P_R2_ADP_PROBE BIT(25)
+ #define U2P_R2_ADP_SENSE BIT(26)
+ #define U2P_R2_SESSION_END BIT(27)
+ #define U2P_R2_VBUS_VALID BIT(28)
+ #define U2P_R2_B_VALID BIT(29)
+ #define U2P_R2_A_VALID BIT(30)
+ #define U2P_R2_ID_DIG BIT(31)
+
+#define U2P_R3 0xc
+
+#define RESET_COMPLETE_TIME 500
+
+struct phy_meson_gxl_usb2_priv {
+ struct regmap *regmap;
+ enum phy_mode mode;
+ int is_enabled;
+ struct clk *clk;
+ struct reset_control *reset;
+};
+
+static const struct regmap_config phy_meson_gxl_usb2_regmap_conf = {
+ .reg_bits = 8,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = U2P_R3,
+};
+
+static int phy_meson_gxl_usb2_init(struct phy *phy)
+{
+ struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ ret = reset_control_reset(priv->reset);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(priv->clk);
+ if (ret) {
+ reset_control_rearm(priv->reset);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int phy_meson_gxl_usb2_exit(struct phy *phy)
+{
+ struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy);
+
+ clk_disable_unprepare(priv->clk);
+ reset_control_rearm(priv->reset);
+
+ return 0;
+}
+
+static int phy_meson_gxl_usb2_reset(struct phy *phy)
+{
+ struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy);
+
+ if (priv->is_enabled) {
+ /* reset the PHY and wait until settings are stabilized */
+ regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_POWER_ON_RESET,
+ U2P_R0_POWER_ON_RESET);
+ udelay(RESET_COMPLETE_TIME);
+ regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_POWER_ON_RESET,
+ 0);
+ udelay(RESET_COMPLETE_TIME);
+ }
+
+ return 0;
+}
+
+static int phy_meson_gxl_usb2_set_mode(struct phy *phy,
+ enum phy_mode mode, int submode)
+{
+ struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy);
+
+ switch (mode) {
+ case PHY_MODE_USB_HOST:
+ case PHY_MODE_USB_OTG:
+ regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_DM_PULLDOWN,
+ U2P_R0_DM_PULLDOWN);
+ regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_DP_PULLDOWN,
+ U2P_R0_DP_PULLDOWN);
+ regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_ID_PULLUP,
+ U2P_R0_ID_PULLUP);
+ break;
+
+ case PHY_MODE_USB_DEVICE:
+ regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_DM_PULLDOWN,
+ 0);
+ regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_DP_PULLDOWN,
+ 0);
+ regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_ID_PULLUP,
+ U2P_R0_ID_PULLUP);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ phy_meson_gxl_usb2_reset(phy);
+
+ priv->mode = mode;
+
+ return 0;
+}
+
+static int phy_meson_gxl_usb2_power_off(struct phy *phy)
+{
+ struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy);
+
+ priv->is_enabled = 0;
+
+ /* power off the PHY by putting it into reset mode */
+ regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_POWER_ON_RESET,
+ U2P_R0_POWER_ON_RESET);
+
+ return 0;
+}
+
+static int phy_meson_gxl_usb2_power_on(struct phy *phy)
+{
+ struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ priv->is_enabled = 1;
+
+ /* power on the PHY by taking it out of reset mode */
+ regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_POWER_ON_RESET, 0);
+
+ ret = phy_meson_gxl_usb2_set_mode(phy, priv->mode, 0);
+ if (ret) {
+ phy_meson_gxl_usb2_power_off(phy);
+
+ dev_err(&phy->dev, "Failed to initialize PHY with mode %d\n",
+ priv->mode);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct phy_ops phy_meson_gxl_usb2_ops = {
+ .init = phy_meson_gxl_usb2_init,
+ .exit = phy_meson_gxl_usb2_exit,
+ .power_on = phy_meson_gxl_usb2_power_on,
+ .power_off = phy_meson_gxl_usb2_power_off,
+ .set_mode = phy_meson_gxl_usb2_set_mode,
+ .reset = phy_meson_gxl_usb2_reset,
+ .owner = THIS_MODULE,
+};
+
+static int phy_meson_gxl_usb2_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct phy_provider *phy_provider;
+ struct phy_meson_gxl_usb2_priv *priv;
+ struct phy *phy;
+ void __iomem *base;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ /* start in host mode */
+ priv->mode = PHY_MODE_USB_HOST;
+
+ priv->regmap = devm_regmap_init_mmio(dev, base,
+ &phy_meson_gxl_usb2_regmap_conf);
+ if (IS_ERR(priv->regmap))
+ return PTR_ERR(priv->regmap);
+
+ priv->clk = devm_clk_get_optional(dev, "phy");
+ if (IS_ERR(priv->clk))
+ return PTR_ERR(priv->clk);
+
+ priv->reset = devm_reset_control_get_optional_shared(dev, "phy");
+ if (IS_ERR(priv->reset))
+ return PTR_ERR(priv->reset);
+
+ phy = devm_phy_create(dev, NULL, &phy_meson_gxl_usb2_ops);
+ if (IS_ERR(phy)) {
+ ret = PTR_ERR(phy);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to create PHY\n");
+
+ return ret;
+ }
+
+ phy_set_drvdata(phy, priv);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id phy_meson_gxl_usb2_of_match[] = {
+ { .compatible = "amlogic,meson-gxl-usb2-phy", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, phy_meson_gxl_usb2_of_match);
+
+static struct platform_driver phy_meson_gxl_usb2_driver = {
+ .probe = phy_meson_gxl_usb2_probe,
+ .driver = {
+ .name = "phy-meson-gxl-usb2",
+ .of_match_table = phy_meson_gxl_usb2_of_match,
+ },
+};
+module_platform_driver(phy_meson_gxl_usb2_driver);
+
+MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
+MODULE_DESCRIPTION("Meson GXL and GXM USB2 PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/amlogic/phy-meson8-hdmi-tx.c b/drivers/phy/amlogic/phy-meson8-hdmi-tx.c
new file mode 100644
index 0000000000..2617f7f6c2
--- /dev/null
+++ b/drivers/phy/amlogic/phy-meson8-hdmi-tx.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Meson8, Meson8b and Meson8m2 HDMI TX PHY.
+ *
+ * Copyright (C) 2021 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+/*
+ * Unfortunately there is no detailed documentation available for the
+ * HHI_HDMI_PHY_CNTL0 register. CTL0 and CTL1 is all we know about.
+ * Magic register values in the driver below are taken from the vendor
+ * BSP / kernel.
+ */
+#define HHI_HDMI_PHY_CNTL0 0x3a0
+ #define HHI_HDMI_PHY_CNTL0_HDMI_CTL1 GENMASK(31, 16)
+ #define HHI_HDMI_PHY_CNTL0_HDMI_CTL0 GENMASK(15, 0)
+
+#define HHI_HDMI_PHY_CNTL1 0x3a4
+ #define HHI_HDMI_PHY_CNTL1_CLOCK_ENABLE BIT(1)
+ #define HHI_HDMI_PHY_CNTL1_SOFT_RESET BIT(0)
+
+#define HHI_HDMI_PHY_CNTL2 0x3a8
+
+struct phy_meson8_hdmi_tx_priv {
+ struct regmap *hhi;
+ struct clk *tmds_clk;
+};
+
+static int phy_meson8_hdmi_tx_init(struct phy *phy)
+{
+ struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy);
+
+ return clk_prepare_enable(priv->tmds_clk);
+}
+
+static int phy_meson8_hdmi_tx_exit(struct phy *phy)
+{
+ struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy);
+
+ clk_disable_unprepare(priv->tmds_clk);
+
+ return 0;
+}
+
+static int phy_meson8_hdmi_tx_power_on(struct phy *phy)
+{
+ struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy);
+ unsigned int i;
+ u16 hdmi_ctl0;
+
+ if (clk_get_rate(priv->tmds_clk) >= 2970UL * 1000 * 1000)
+ hdmi_ctl0 = 0x1e8b;
+ else
+ hdmi_ctl0 = 0x4d0b;
+
+ regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0,
+ FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL1, 0x08c3) |
+ FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL0, hdmi_ctl0));
+
+ regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1, 0x0);
+
+ /* Reset three times, just like the vendor driver does */
+ for (i = 0; i < 3; i++) {
+ regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1,
+ HHI_HDMI_PHY_CNTL1_CLOCK_ENABLE |
+ HHI_HDMI_PHY_CNTL1_SOFT_RESET);
+ usleep_range(1000, 2000);
+
+ regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1,
+ HHI_HDMI_PHY_CNTL1_CLOCK_ENABLE);
+ usleep_range(1000, 2000);
+ }
+
+ return 0;
+}
+
+static int phy_meson8_hdmi_tx_power_off(struct phy *phy)
+{
+ struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy);
+
+ regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0,
+ FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL1, 0x0841) |
+ FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL0, 0x8d00));
+
+ return 0;
+}
+
+static const struct phy_ops phy_meson8_hdmi_tx_ops = {
+ .init = phy_meson8_hdmi_tx_init,
+ .exit = phy_meson8_hdmi_tx_exit,
+ .power_on = phy_meson8_hdmi_tx_power_on,
+ .power_off = phy_meson8_hdmi_tx_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int phy_meson8_hdmi_tx_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct phy_meson8_hdmi_tx_priv *priv;
+ struct phy_provider *phy_provider;
+ struct resource *res;
+ struct phy *phy;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -EINVAL;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->hhi = syscon_node_to_regmap(np->parent);
+ if (IS_ERR(priv->hhi))
+ return PTR_ERR(priv->hhi);
+
+ priv->tmds_clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(priv->tmds_clk))
+ return PTR_ERR(priv->tmds_clk);
+
+ phy = devm_phy_create(&pdev->dev, np, &phy_meson8_hdmi_tx_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);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id phy_meson8_hdmi_tx_of_match[] = {
+ { .compatible = "amlogic,meson8-hdmi-tx-phy" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, phy_meson8_hdmi_tx_of_match);
+
+static struct platform_driver phy_meson8_hdmi_tx_driver = {
+ .probe = phy_meson8_hdmi_tx_probe,
+ .driver = {
+ .name = "phy-meson8-hdmi-tx",
+ .of_match_table = phy_meson8_hdmi_tx_of_match,
+ },
+};
+module_platform_driver(phy_meson8_hdmi_tx_driver);
+
+MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
+MODULE_DESCRIPTION("Meson8, Meson8b and Meson8m2 HDMI TX PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/amlogic/phy-meson8b-usb2.c b/drivers/phy/amlogic/phy-meson8b-usb2.c
new file mode 100644
index 0000000000..d63147c41b
--- /dev/null
+++ b/drivers/phy/amlogic/phy-meson8b-usb2.c
@@ -0,0 +1,337 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Meson8, Meson8b and GXBB USB2 PHY driver
+ *
+ * Copyright (C) 2016 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/usb/of.h>
+
+#define REG_CONFIG 0x00
+ #define REG_CONFIG_CLK_EN BIT(0)
+ #define REG_CONFIG_CLK_SEL_MASK GENMASK(3, 1)
+ #define REG_CONFIG_CLK_DIV_MASK GENMASK(10, 4)
+ #define REG_CONFIG_CLK_32k_ALTSEL BIT(15)
+ #define REG_CONFIG_TEST_TRIG BIT(31)
+
+#define REG_CTRL 0x04
+ #define REG_CTRL_SOFT_PRST BIT(0)
+ #define REG_CTRL_SOFT_HRESET BIT(1)
+ #define REG_CTRL_SS_SCALEDOWN_MODE_MASK GENMASK(3, 2)
+ #define REG_CTRL_CLK_DET_RST BIT(4)
+ #define REG_CTRL_INTR_SEL BIT(5)
+ #define REG_CTRL_CLK_DETECTED BIT(8)
+ #define REG_CTRL_SOF_SENT_RCVD_TGL BIT(9)
+ #define REG_CTRL_SOF_TOGGLE_OUT BIT(10)
+ #define REG_CTRL_POWER_ON_RESET BIT(15)
+ #define REG_CTRL_SLEEPM BIT(16)
+ #define REG_CTRL_TX_BITSTUFF_ENN_H BIT(17)
+ #define REG_CTRL_TX_BITSTUFF_ENN BIT(18)
+ #define REG_CTRL_COMMON_ON BIT(19)
+ #define REG_CTRL_REF_CLK_SEL_MASK GENMASK(21, 20)
+ #define REG_CTRL_REF_CLK_SEL_SHIFT 20
+ #define REG_CTRL_FSEL_MASK GENMASK(24, 22)
+ #define REG_CTRL_FSEL_SHIFT 22
+ #define REG_CTRL_PORT_RESET BIT(25)
+ #define REG_CTRL_THREAD_ID_MASK GENMASK(31, 26)
+
+#define REG_ENDP_INTR 0x08
+
+/* bits [31:26], [24:21] and [15:3] seem to be read-only */
+#define REG_ADP_BC 0x0c
+ #define REG_ADP_BC_VBUS_VLD_EXT_SEL BIT(0)
+ #define REG_ADP_BC_VBUS_VLD_EXT BIT(1)
+ #define REG_ADP_BC_OTG_DISABLE BIT(2)
+ #define REG_ADP_BC_ID_PULLUP BIT(3)
+ #define REG_ADP_BC_DRV_VBUS BIT(4)
+ #define REG_ADP_BC_ADP_PRB_EN BIT(5)
+ #define REG_ADP_BC_ADP_DISCHARGE BIT(6)
+ #define REG_ADP_BC_ADP_CHARGE BIT(7)
+ #define REG_ADP_BC_SESS_END BIT(8)
+ #define REG_ADP_BC_DEVICE_SESS_VLD BIT(9)
+ #define REG_ADP_BC_B_VALID BIT(10)
+ #define REG_ADP_BC_A_VALID BIT(11)
+ #define REG_ADP_BC_ID_DIG BIT(12)
+ #define REG_ADP_BC_VBUS_VALID BIT(13)
+ #define REG_ADP_BC_ADP_PROBE BIT(14)
+ #define REG_ADP_BC_ADP_SENSE BIT(15)
+ #define REG_ADP_BC_ACA_ENABLE BIT(16)
+ #define REG_ADP_BC_DCD_ENABLE BIT(17)
+ #define REG_ADP_BC_VDAT_DET_EN_B BIT(18)
+ #define REG_ADP_BC_VDAT_SRC_EN_B BIT(19)
+ #define REG_ADP_BC_CHARGE_SEL BIT(20)
+ #define REG_ADP_BC_CHARGE_DETECT BIT(21)
+ #define REG_ADP_BC_ACA_PIN_RANGE_C BIT(22)
+ #define REG_ADP_BC_ACA_PIN_RANGE_B BIT(23)
+ #define REG_ADP_BC_ACA_PIN_RANGE_A BIT(24)
+ #define REG_ADP_BC_ACA_PIN_GND BIT(25)
+ #define REG_ADP_BC_ACA_PIN_FLOAT BIT(26)
+
+#define REG_DBG_UART 0x10
+ #define REG_DBG_UART_BYPASS_SEL BIT(0)
+ #define REG_DBG_UART_BYPASS_DM_EN BIT(1)
+ #define REG_DBG_UART_BYPASS_DP_EN BIT(2)
+ #define REG_DBG_UART_BYPASS_DM_DATA BIT(3)
+ #define REG_DBG_UART_BYPASS_DP_DATA BIT(4)
+ #define REG_DBG_UART_FSV_MINUS BIT(5)
+ #define REG_DBG_UART_FSV_PLUS BIT(6)
+ #define REG_DBG_UART_FSV_BURN_IN_TEST BIT(7)
+ #define REG_DBG_UART_LOOPBACK_EN_B BIT(8)
+ #define REG_DBG_UART_SET_IDDQ BIT(9)
+ #define REG_DBG_UART_ATE_RESET BIT(10)
+
+#define REG_TEST 0x14
+ #define REG_TEST_DATA_IN_MASK GENMASK(3, 0)
+ #define REG_TEST_EN_MASK GENMASK(7, 4)
+ #define REG_TEST_ADDR_MASK GENMASK(11, 8)
+ #define REG_TEST_DATA_OUT_SEL BIT(12)
+ #define REG_TEST_CLK BIT(13)
+ #define REG_TEST_VA_TEST_EN_B_MASK GENMASK(15, 14)
+ #define REG_TEST_DATA_OUT_MASK GENMASK(19, 16)
+ #define REG_TEST_DISABLE_ID_PULLUP BIT(20)
+
+#define REG_TUNE 0x18
+ #define REG_TUNE_TX_RES_TUNE_MASK GENMASK(1, 0)
+ #define REG_TUNE_TX_HSXV_TUNE_MASK GENMASK(3, 2)
+ #define REG_TUNE_TX_VREF_TUNE_MASK GENMASK(7, 4)
+ #define REG_TUNE_TX_RISE_TUNE_MASK GENMASK(9, 8)
+ #define REG_TUNE_TX_PREEMP_PULSE_TUNE BIT(10)
+ #define REG_TUNE_TX_PREEMP_AMP_TUNE_MASK GENMASK(12, 11)
+ #define REG_TUNE_TX_FSLS_TUNE_MASK GENMASK(16, 13)
+ #define REG_TUNE_SQRX_TUNE_MASK GENMASK(19, 17)
+ #define REG_TUNE_OTG_TUNE GENMASK(22, 20)
+ #define REG_TUNE_COMP_DIS_TUNE GENMASK(25, 23)
+ #define REG_TUNE_HOST_DM_PULLDOWN BIT(26)
+ #define REG_TUNE_HOST_DP_PULLDOWN BIT(27)
+
+#define RESET_COMPLETE_TIME 500
+#define ACA_ENABLE_COMPLETE_TIME 50
+
+struct phy_meson8b_usb2_match_data {
+ bool host_enable_aca;
+};
+
+struct phy_meson8b_usb2_priv {
+ struct regmap *regmap;
+ enum usb_dr_mode dr_mode;
+ struct clk *clk_usb_general;
+ struct clk *clk_usb;
+ struct reset_control *reset;
+ const struct phy_meson8b_usb2_match_data *match;
+};
+
+static const struct regmap_config phy_meson8b_usb2_regmap_conf = {
+ .reg_bits = 8,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = REG_TUNE,
+};
+
+static int phy_meson8b_usb2_power_on(struct phy *phy)
+{
+ struct phy_meson8b_usb2_priv *priv = phy_get_drvdata(phy);
+ u32 reg;
+ int ret;
+
+ if (!IS_ERR_OR_NULL(priv->reset)) {
+ ret = reset_control_reset(priv->reset);
+ if (ret) {
+ dev_err(&phy->dev, "Failed to trigger USB reset\n");
+ return ret;
+ }
+ }
+
+ ret = clk_prepare_enable(priv->clk_usb_general);
+ if (ret) {
+ dev_err(&phy->dev, "Failed to enable USB general clock\n");
+ reset_control_rearm(priv->reset);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(priv->clk_usb);
+ if (ret) {
+ dev_err(&phy->dev, "Failed to enable USB DDR clock\n");
+ clk_disable_unprepare(priv->clk_usb_general);
+ reset_control_rearm(priv->reset);
+ return ret;
+ }
+
+ regmap_update_bits(priv->regmap, REG_CONFIG, REG_CONFIG_CLK_32k_ALTSEL,
+ REG_CONFIG_CLK_32k_ALTSEL);
+
+ regmap_update_bits(priv->regmap, REG_CTRL, REG_CTRL_REF_CLK_SEL_MASK,
+ 0x2 << REG_CTRL_REF_CLK_SEL_SHIFT);
+
+ regmap_update_bits(priv->regmap, REG_CTRL, REG_CTRL_FSEL_MASK,
+ 0x5 << REG_CTRL_FSEL_SHIFT);
+
+ /* reset the PHY */
+ regmap_update_bits(priv->regmap, REG_CTRL, REG_CTRL_POWER_ON_RESET,
+ REG_CTRL_POWER_ON_RESET);
+ udelay(RESET_COMPLETE_TIME);
+ regmap_update_bits(priv->regmap, REG_CTRL, REG_CTRL_POWER_ON_RESET, 0);
+ udelay(RESET_COMPLETE_TIME);
+
+ regmap_update_bits(priv->regmap, REG_CTRL, REG_CTRL_SOF_TOGGLE_OUT,
+ REG_CTRL_SOF_TOGGLE_OUT);
+
+ if (priv->dr_mode == USB_DR_MODE_HOST) {
+ regmap_update_bits(priv->regmap, REG_DBG_UART,
+ REG_DBG_UART_SET_IDDQ, 0);
+
+ if (priv->match->host_enable_aca) {
+ regmap_update_bits(priv->regmap, REG_ADP_BC,
+ REG_ADP_BC_ACA_ENABLE,
+ REG_ADP_BC_ACA_ENABLE);
+
+ udelay(ACA_ENABLE_COMPLETE_TIME);
+
+ regmap_read(priv->regmap, REG_ADP_BC, &reg);
+ if (reg & REG_ADP_BC_ACA_PIN_FLOAT) {
+ dev_warn(&phy->dev, "USB ID detect failed!\n");
+ clk_disable_unprepare(priv->clk_usb);
+ clk_disable_unprepare(priv->clk_usb_general);
+ reset_control_rearm(priv->reset);
+ return -EINVAL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int phy_meson8b_usb2_power_off(struct phy *phy)
+{
+ struct phy_meson8b_usb2_priv *priv = phy_get_drvdata(phy);
+
+ if (priv->dr_mode == USB_DR_MODE_HOST)
+ regmap_update_bits(priv->regmap, REG_DBG_UART,
+ REG_DBG_UART_SET_IDDQ,
+ REG_DBG_UART_SET_IDDQ);
+
+ clk_disable_unprepare(priv->clk_usb);
+ clk_disable_unprepare(priv->clk_usb_general);
+ reset_control_rearm(priv->reset);
+
+ /* power off the PHY by putting it into reset mode */
+ regmap_update_bits(priv->regmap, REG_CTRL, REG_CTRL_POWER_ON_RESET,
+ REG_CTRL_POWER_ON_RESET);
+
+ return 0;
+}
+
+static const struct phy_ops phy_meson8b_usb2_ops = {
+ .power_on = phy_meson8b_usb2_power_on,
+ .power_off = phy_meson8b_usb2_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int phy_meson8b_usb2_probe(struct platform_device *pdev)
+{
+ struct phy_meson8b_usb2_priv *priv;
+ struct phy *phy;
+ struct phy_provider *phy_provider;
+ 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->match = device_get_match_data(&pdev->dev);
+ if (!priv->match)
+ return -ENODEV;
+
+ priv->regmap = devm_regmap_init_mmio(&pdev->dev, base,
+ &phy_meson8b_usb2_regmap_conf);
+ if (IS_ERR(priv->regmap))
+ return PTR_ERR(priv->regmap);
+
+ priv->clk_usb_general = devm_clk_get(&pdev->dev, "usb_general");
+ if (IS_ERR(priv->clk_usb_general))
+ return PTR_ERR(priv->clk_usb_general);
+
+ priv->clk_usb = devm_clk_get(&pdev->dev, "usb");
+ if (IS_ERR(priv->clk_usb))
+ return PTR_ERR(priv->clk_usb);
+
+ priv->reset = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
+ if (IS_ERR(priv->reset))
+ return dev_err_probe(&pdev->dev, PTR_ERR(priv->reset),
+ "Failed to get the reset line");
+
+ priv->dr_mode = of_usb_get_dr_mode_by_phy(pdev->dev.of_node, -1);
+ if (priv->dr_mode == USB_DR_MODE_UNKNOWN) {
+ dev_err(&pdev->dev,
+ "missing dual role configuration of the controller\n");
+ return -EINVAL;
+ }
+
+ phy = devm_phy_create(&pdev->dev, NULL, &phy_meson8b_usb2_ops);
+ if (IS_ERR(phy)) {
+ return dev_err_probe(&pdev->dev, PTR_ERR(phy),
+ "failed to create PHY\n");
+ }
+
+ 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 const struct phy_meson8b_usb2_match_data phy_meson8_usb2_match_data = {
+ .host_enable_aca = false,
+};
+
+static const struct phy_meson8b_usb2_match_data phy_meson8b_usb2_match_data = {
+ .host_enable_aca = true,
+};
+
+static const struct of_device_id phy_meson8b_usb2_of_match[] = {
+ {
+ .compatible = "amlogic,meson8-usb2-phy",
+ .data = &phy_meson8_usb2_match_data
+ },
+ {
+ .compatible = "amlogic,meson8b-usb2-phy",
+ .data = &phy_meson8b_usb2_match_data
+ },
+ {
+ .compatible = "amlogic,meson8m2-usb2-phy",
+ .data = &phy_meson8b_usb2_match_data
+ },
+ {
+ .compatible = "amlogic,meson-gxbb-usb2-phy",
+ .data = &phy_meson8b_usb2_match_data
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, phy_meson8b_usb2_of_match);
+
+static struct platform_driver phy_meson8b_usb2_driver = {
+ .probe = phy_meson8b_usb2_probe,
+ .driver = {
+ .name = "phy-meson-usb2",
+ .of_match_table = phy_meson8b_usb2_of_match,
+ },
+};
+module_platform_driver(phy_meson8b_usb2_driver);
+
+MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
+MODULE_DESCRIPTION("Meson8, Meson8b, Meson8m2 and GXBB USB2 PHY driver");
+MODULE_LICENSE("GPL");