summaryrefslogtreecommitdiffstats
path: root/drivers/phy/st
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/st
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 '')
-rw-r--r--drivers/phy/st/Kconfig49
-rw-r--r--drivers/phy/st/Makefile6
-rw-r--r--drivers/phy/st/phy-miphy28lp.c1260
-rw-r--r--drivers/phy/st/phy-spear1310-miphy.c258
-rw-r--r--drivers/phy/st/phy-spear1340-miphy.c291
-rw-r--r--drivers/phy/st/phy-stih407-usb.c176
-rw-r--r--drivers/phy/st/phy-stm32-usbphyc.c826
-rw-r--r--drivers/phy/starfive/Kconfig38
-rw-r--r--drivers/phy/starfive/Makefile4
-rw-r--r--drivers/phy/starfive/phy-jh7110-dphy-rx.c232
-rw-r--r--drivers/phy/starfive/phy-jh7110-pcie.c204
-rw-r--r--drivers/phy/starfive/phy-jh7110-usb.c152
12 files changed, 3496 insertions, 0 deletions
diff --git a/drivers/phy/st/Kconfig b/drivers/phy/st/Kconfig
new file mode 100644
index 0000000000..3fc3d0781f
--- /dev/null
+++ b/drivers/phy/st/Kconfig
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Phy drivers for STMicro platforms
+#
+config PHY_MIPHY28LP
+ tristate "STMicroelectronics MIPHY28LP PHY driver for STiH407"
+ depends on ARCH_STI
+ select GENERIC_PHY
+ help
+ Enable this to support the miphy transceiver (for SATA/PCIE/USB3)
+ that is part of STMicroelectronics STiH407 SoC.
+
+config PHY_ST_SPEAR1310_MIPHY
+ tristate "ST SPEAR1310-MIPHY driver"
+ select GENERIC_PHY
+ depends on MACH_SPEAR1310 || COMPILE_TEST
+ help
+ Support for ST SPEAr1310 MIPHY which can be used for PCIe and SATA.
+
+config PHY_ST_SPEAR1340_MIPHY
+ tristate "ST SPEAR1340-MIPHY driver"
+ select GENERIC_PHY
+ depends on MACH_SPEAR1340 || COMPILE_TEST
+ help
+ Support for ST SPEAr1340 MIPHY which can be used for PCIe and SATA.
+
+config PHY_STIH407_USB
+ tristate "STMicroelectronics USB2 picoPHY driver for STiH407 family"
+ depends on RESET_CONTROLLER
+ depends on ARCH_STI || COMPILE_TEST
+ select GENERIC_PHY
+ help
+ Enable this support to enable the picoPHY device used by USB2
+ and USB3 controllers on STMicroelectronics STiH407 SoC families.
+
+config PHY_STM32_USBPHYC
+ tristate "STMicroelectronics STM32 USB HS PHY Controller driver"
+ depends on ARCH_STM32 || COMPILE_TEST
+ depends on COMMON_CLK
+ select GENERIC_PHY
+ help
+ Enable this to support the High-Speed USB transceivers that are part
+ of some STMicroelectronics STM32 SoCs.
+
+ This driver controls the entire USB PHY block: the USB PHY controller
+ (USBPHYC) and the two 8-bit wide UTMI+ interfaces. First interface is
+ used by an HS USB Host controller, and the second one is shared
+ between an HS USB OTG controller and an HS USB Host controller,
+ selected by a USB switch.
diff --git a/drivers/phy/st/Makefile b/drivers/phy/st/Makefile
new file mode 100644
index 0000000000..c862dd937b
--- /dev/null
+++ b/drivers/phy/st/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_PHY_MIPHY28LP) += phy-miphy28lp.o
+obj-$(CONFIG_PHY_ST_SPEAR1310_MIPHY) += phy-spear1310-miphy.o
+obj-$(CONFIG_PHY_ST_SPEAR1340_MIPHY) += phy-spear1340-miphy.o
+obj-$(CONFIG_PHY_STIH407_USB) += phy-stih407-usb.o
+obj-$(CONFIG_PHY_STM32_USBPHYC) += phy-stm32-usbphyc.o
diff --git a/drivers/phy/st/phy-miphy28lp.c b/drivers/phy/st/phy-miphy28lp.c
new file mode 100644
index 0000000000..e30305b77f
--- /dev/null
+++ b/drivers/phy/st/phy-miphy28lp.c
@@ -0,0 +1,1260 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2014 STMicroelectronics
+ *
+ * STMicroelectronics PHY driver MiPHY28lp (for SoC STiH407).
+ *
+ * Author: Alexandre Torgue <alexandre.torgue@st.com>
+ */
+
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_address.h>
+#include <linux/clk.h>
+#include <linux/phy/phy.h>
+#include <linux/delay.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#include <dt-bindings/phy/phy.h>
+
+/* MiPHY registers */
+#define MIPHY_CONF_RESET 0x00
+#define RST_APPLI_SW BIT(0)
+#define RST_CONF_SW BIT(1)
+#define RST_MACRO_SW BIT(2)
+
+#define MIPHY_RESET 0x01
+#define RST_PLL_SW BIT(0)
+#define RST_COMP_SW BIT(2)
+
+#define MIPHY_STATUS_1 0x02
+#define PHY_RDY BIT(0)
+#define HFC_RDY BIT(1)
+#define HFC_PLL BIT(2)
+
+#define MIPHY_CONTROL 0x04
+#define TERM_EN_SW BIT(2)
+#define DIS_LINK_RST BIT(3)
+#define AUTO_RST_RX BIT(4)
+#define PX_RX_POL BIT(5)
+
+#define MIPHY_BOUNDARY_SEL 0x0a
+#define TX_SEL BIT(6)
+#define SSC_SEL BIT(4)
+#define GENSEL_SEL BIT(0)
+
+#define MIPHY_BOUNDARY_1 0x0b
+#define MIPHY_BOUNDARY_2 0x0c
+#define SSC_EN_SW BIT(2)
+
+#define MIPHY_PLL_CLKREF_FREQ 0x0d
+#define MIPHY_SPEED 0x0e
+#define TX_SPDSEL_80DEC 0
+#define TX_SPDSEL_40DEC 1
+#define TX_SPDSEL_20DEC 2
+#define RX_SPDSEL_80DEC 0
+#define RX_SPDSEL_40DEC (1 << 2)
+#define RX_SPDSEL_20DEC (2 << 2)
+
+#define MIPHY_CONF 0x0f
+#define MIPHY_CTRL_TEST_SEL 0x20
+#define MIPHY_CTRL_TEST_1 0x21
+#define MIPHY_CTRL_TEST_2 0x22
+#define MIPHY_CTRL_TEST_3 0x23
+#define MIPHY_CTRL_TEST_4 0x24
+#define MIPHY_FEEDBACK_TEST 0x25
+#define MIPHY_DEBUG_BUS 0x26
+#define MIPHY_DEBUG_STATUS_MSB 0x27
+#define MIPHY_DEBUG_STATUS_LSB 0x28
+#define MIPHY_PWR_RAIL_1 0x29
+#define MIPHY_PWR_RAIL_2 0x2a
+#define MIPHY_SYNCHAR_CONTROL 0x30
+
+#define MIPHY_COMP_FSM_1 0x3a
+#define COMP_START BIT(6)
+
+#define MIPHY_COMP_FSM_6 0x3f
+#define COMP_DONE BIT(7)
+
+#define MIPHY_COMP_POSTP 0x42
+#define MIPHY_TX_CTRL_1 0x49
+#define TX_REG_STEP_0V 0
+#define TX_REG_STEP_P_25MV 1
+#define TX_REG_STEP_P_50MV 2
+#define TX_REG_STEP_N_25MV 7
+#define TX_REG_STEP_N_50MV 6
+#define TX_REG_STEP_N_75MV 5
+
+#define MIPHY_TX_CTRL_2 0x4a
+#define TX_SLEW_SW_40_PS 0
+#define TX_SLEW_SW_80_PS 1
+#define TX_SLEW_SW_120_PS 2
+
+#define MIPHY_TX_CTRL_3 0x4b
+#define MIPHY_TX_CAL_MAN 0x4e
+#define TX_SLEW_CAL_MAN_EN BIT(0)
+
+#define MIPHY_TST_BIAS_BOOST_2 0x62
+#define MIPHY_BIAS_BOOST_1 0x63
+#define MIPHY_BIAS_BOOST_2 0x64
+#define MIPHY_RX_DESBUFF_FDB_2 0x67
+#define MIPHY_RX_DESBUFF_FDB_3 0x68
+#define MIPHY_SIGDET_COMPENS1 0x69
+#define MIPHY_SIGDET_COMPENS2 0x6a
+#define MIPHY_JITTER_PERIOD 0x6b
+#define MIPHY_JITTER_AMPLITUDE_1 0x6c
+#define MIPHY_JITTER_AMPLITUDE_2 0x6d
+#define MIPHY_JITTER_AMPLITUDE_3 0x6e
+#define MIPHY_RX_K_GAIN 0x78
+#define MIPHY_RX_BUFFER_CTRL 0x7a
+#define VGA_GAIN BIT(0)
+#define EQ_DC_GAIN BIT(2)
+#define EQ_BOOST_GAIN BIT(3)
+
+#define MIPHY_RX_VGA_GAIN 0x7b
+#define MIPHY_RX_EQU_GAIN_1 0x7f
+#define MIPHY_RX_EQU_GAIN_2 0x80
+#define MIPHY_RX_EQU_GAIN_3 0x81
+#define MIPHY_RX_CAL_CTRL_1 0x97
+#define MIPHY_RX_CAL_CTRL_2 0x98
+
+#define MIPHY_RX_CAL_OFFSET_CTRL 0x99
+#define CAL_OFFSET_VGA_64 (0x03 << 0)
+#define CAL_OFFSET_THRESHOLD_64 (0x03 << 2)
+#define VGA_OFFSET_POLARITY BIT(4)
+#define OFFSET_COMPENSATION_EN BIT(6)
+
+#define MIPHY_RX_CAL_VGA_STEP 0x9a
+#define MIPHY_RX_CAL_EYE_MIN 0x9d
+#define MIPHY_RX_CAL_OPT_LENGTH 0x9f
+#define MIPHY_RX_LOCK_CTRL_1 0xc1
+#define MIPHY_RX_LOCK_SETTINGS_OPT 0xc2
+#define MIPHY_RX_LOCK_STEP 0xc4
+
+#define MIPHY_RX_SIGDET_SLEEP_OA 0xc9
+#define MIPHY_RX_SIGDET_SLEEP_SEL 0xca
+#define MIPHY_RX_SIGDET_WAIT_SEL 0xcb
+#define MIPHY_RX_SIGDET_DATA_SEL 0xcc
+#define EN_ULTRA_LOW_POWER BIT(0)
+#define EN_FIRST_HALF BIT(1)
+#define EN_SECOND_HALF BIT(2)
+#define EN_DIGIT_SIGNAL_CHECK BIT(3)
+
+#define MIPHY_RX_POWER_CTRL_1 0xcd
+#define MIPHY_RX_POWER_CTRL_2 0xce
+#define MIPHY_PLL_CALSET_CTRL 0xd3
+#define MIPHY_PLL_CALSET_1 0xd4
+#define MIPHY_PLL_CALSET_2 0xd5
+#define MIPHY_PLL_CALSET_3 0xd6
+#define MIPHY_PLL_CALSET_4 0xd7
+#define MIPHY_PLL_SBR_1 0xe3
+#define SET_NEW_CHANGE BIT(1)
+
+#define MIPHY_PLL_SBR_2 0xe4
+#define MIPHY_PLL_SBR_3 0xe5
+#define MIPHY_PLL_SBR_4 0xe6
+#define MIPHY_PLL_COMMON_MISC_2 0xe9
+#define START_ACT_FILT BIT(6)
+
+#define MIPHY_PLL_SPAREIN 0xeb
+
+/*
+ * On STiH407 the glue logic can be different among MiPHY devices; for example:
+ * MiPHY0: OSC_FORCE_EXT means:
+ * 0: 30MHz crystal clk - 1: 100MHz ext clk routed through MiPHY1
+ * MiPHY1: OSC_FORCE_EXT means:
+ * 1: 30MHz crystal clk - 0: 100MHz ext clk routed through MiPHY1
+ * Some devices have not the possibility to check if the osc is ready.
+ */
+#define MIPHY_OSC_FORCE_EXT BIT(3)
+#define MIPHY_OSC_RDY BIT(5)
+
+#define MIPHY_CTRL_MASK 0x0f
+#define MIPHY_CTRL_DEFAULT 0
+#define MIPHY_CTRL_SYNC_D_EN BIT(2)
+
+/* SATA / PCIe defines */
+#define SATA_CTRL_MASK 0x07
+#define PCIE_CTRL_MASK 0xff
+#define SATA_CTRL_SELECT_SATA 1
+#define SATA_CTRL_SELECT_PCIE 0
+#define SYSCFG_PCIE_PCIE_VAL 0x80
+#define SATA_SPDMODE 1
+
+#define MIPHY_SATA_BANK_NB 3
+#define MIPHY_PCIE_BANK_NB 2
+
+enum {
+ SYSCFG_CTRL,
+ SYSCFG_STATUS,
+ SYSCFG_PCI,
+ SYSCFG_SATA,
+ SYSCFG_REG_MAX,
+};
+
+struct miphy28lp_phy {
+ struct phy *phy;
+ struct miphy28lp_dev *phydev;
+ void __iomem *base;
+ void __iomem *pipebase;
+
+ bool osc_force_ext;
+ bool osc_rdy;
+ bool px_rx_pol_inv;
+ bool ssc;
+ bool tx_impedance;
+
+ struct reset_control *miphy_rst;
+
+ u32 sata_gen;
+
+ /* Sysconfig registers offsets needed to configure the device */
+ u32 syscfg_reg[SYSCFG_REG_MAX];
+ u8 type;
+};
+
+struct miphy28lp_dev {
+ struct device *dev;
+ struct regmap *regmap;
+ struct mutex miphy_mutex;
+ struct miphy28lp_phy **phys;
+ int nphys;
+};
+
+struct miphy_initval {
+ u16 reg;
+ u16 val;
+};
+
+enum miphy_sata_gen { SATA_GEN1, SATA_GEN2, SATA_GEN3 };
+
+static char *PHY_TYPE_name[] = { "sata-up", "pcie-up", "", "usb3-up" };
+
+struct pll_ratio {
+ int clk_ref;
+ int calset_1;
+ int calset_2;
+ int calset_3;
+ int calset_4;
+ int cal_ctrl;
+};
+
+static struct pll_ratio sata_pll_ratio = {
+ .clk_ref = 0x1e,
+ .calset_1 = 0xc8,
+ .calset_2 = 0x00,
+ .calset_3 = 0x00,
+ .calset_4 = 0x00,
+ .cal_ctrl = 0x00,
+};
+
+static struct pll_ratio pcie_pll_ratio = {
+ .clk_ref = 0x1e,
+ .calset_1 = 0xa6,
+ .calset_2 = 0xaa,
+ .calset_3 = 0xaa,
+ .calset_4 = 0x00,
+ .cal_ctrl = 0x00,
+};
+
+static struct pll_ratio usb3_pll_ratio = {
+ .clk_ref = 0x1e,
+ .calset_1 = 0xa6,
+ .calset_2 = 0xaa,
+ .calset_3 = 0xaa,
+ .calset_4 = 0x04,
+ .cal_ctrl = 0x00,
+};
+
+struct miphy28lp_pll_gen {
+ int bank;
+ int speed;
+ int bias_boost_1;
+ int bias_boost_2;
+ int tx_ctrl_1;
+ int tx_ctrl_2;
+ int tx_ctrl_3;
+ int rx_k_gain;
+ int rx_vga_gain;
+ int rx_equ_gain_1;
+ int rx_equ_gain_2;
+ int rx_equ_gain_3;
+ int rx_buff_ctrl;
+};
+
+static struct miphy28lp_pll_gen sata_pll_gen[] = {
+ {
+ .bank = 0x00,
+ .speed = TX_SPDSEL_80DEC | RX_SPDSEL_80DEC,
+ .bias_boost_1 = 0x00,
+ .bias_boost_2 = 0xae,
+ .tx_ctrl_2 = 0x53,
+ .tx_ctrl_3 = 0x00,
+ .rx_buff_ctrl = EQ_BOOST_GAIN | EQ_DC_GAIN | VGA_GAIN,
+ .rx_vga_gain = 0x00,
+ .rx_equ_gain_1 = 0x7d,
+ .rx_equ_gain_2 = 0x56,
+ .rx_equ_gain_3 = 0x00,
+ },
+ {
+ .bank = 0x01,
+ .speed = TX_SPDSEL_40DEC | RX_SPDSEL_40DEC,
+ .bias_boost_1 = 0x00,
+ .bias_boost_2 = 0xae,
+ .tx_ctrl_2 = 0x72,
+ .tx_ctrl_3 = 0x20,
+ .rx_buff_ctrl = EQ_BOOST_GAIN | EQ_DC_GAIN | VGA_GAIN,
+ .rx_vga_gain = 0x00,
+ .rx_equ_gain_1 = 0x7d,
+ .rx_equ_gain_2 = 0x56,
+ .rx_equ_gain_3 = 0x00,
+ },
+ {
+ .bank = 0x02,
+ .speed = TX_SPDSEL_20DEC | RX_SPDSEL_20DEC,
+ .bias_boost_1 = 0x00,
+ .bias_boost_2 = 0xae,
+ .tx_ctrl_2 = 0xc0,
+ .tx_ctrl_3 = 0x20,
+ .rx_buff_ctrl = EQ_BOOST_GAIN | EQ_DC_GAIN | VGA_GAIN,
+ .rx_vga_gain = 0x00,
+ .rx_equ_gain_1 = 0x7d,
+ .rx_equ_gain_2 = 0x56,
+ .rx_equ_gain_3 = 0x00,
+ },
+};
+
+static struct miphy28lp_pll_gen pcie_pll_gen[] = {
+ {
+ .bank = 0x00,
+ .speed = TX_SPDSEL_40DEC | RX_SPDSEL_40DEC,
+ .bias_boost_1 = 0x00,
+ .bias_boost_2 = 0xa5,
+ .tx_ctrl_1 = TX_REG_STEP_N_25MV,
+ .tx_ctrl_2 = 0x71,
+ .tx_ctrl_3 = 0x60,
+ .rx_k_gain = 0x98,
+ .rx_buff_ctrl = EQ_BOOST_GAIN | EQ_DC_GAIN | VGA_GAIN,
+ .rx_vga_gain = 0x00,
+ .rx_equ_gain_1 = 0x79,
+ .rx_equ_gain_2 = 0x56,
+ },
+ {
+ .bank = 0x01,
+ .speed = TX_SPDSEL_20DEC | RX_SPDSEL_20DEC,
+ .bias_boost_1 = 0x00,
+ .bias_boost_2 = 0xa5,
+ .tx_ctrl_1 = TX_REG_STEP_N_25MV,
+ .tx_ctrl_2 = 0x70,
+ .tx_ctrl_3 = 0x60,
+ .rx_k_gain = 0xcc,
+ .rx_buff_ctrl = EQ_BOOST_GAIN | EQ_DC_GAIN | VGA_GAIN,
+ .rx_vga_gain = 0x00,
+ .rx_equ_gain_1 = 0x78,
+ .rx_equ_gain_2 = 0x07,
+ },
+};
+
+static inline void miphy28lp_set_reset(struct miphy28lp_phy *miphy_phy)
+{
+ void __iomem *base = miphy_phy->base;
+ u8 val;
+
+ /* Putting Macro in reset */
+ writeb_relaxed(RST_APPLI_SW, base + MIPHY_CONF_RESET);
+
+ val = RST_APPLI_SW | RST_CONF_SW;
+ writeb_relaxed(val, base + MIPHY_CONF_RESET);
+
+ writeb_relaxed(RST_APPLI_SW, base + MIPHY_CONF_RESET);
+
+ /* Bringing the MIPHY-CPU registers out of reset */
+ if (miphy_phy->type == PHY_TYPE_PCIE) {
+ val = AUTO_RST_RX | TERM_EN_SW;
+ writeb_relaxed(val, base + MIPHY_CONTROL);
+ } else {
+ val = AUTO_RST_RX | TERM_EN_SW | DIS_LINK_RST;
+ writeb_relaxed(val, base + MIPHY_CONTROL);
+ }
+}
+
+static inline void miphy28lp_pll_calibration(struct miphy28lp_phy *miphy_phy,
+ struct pll_ratio *pll_ratio)
+{
+ void __iomem *base = miphy_phy->base;
+ u8 val;
+
+ /* Applying PLL Settings */
+ writeb_relaxed(0x1d, base + MIPHY_PLL_SPAREIN);
+ writeb_relaxed(pll_ratio->clk_ref, base + MIPHY_PLL_CLKREF_FREQ);
+
+ /* PLL Ratio */
+ writeb_relaxed(pll_ratio->calset_1, base + MIPHY_PLL_CALSET_1);
+ writeb_relaxed(pll_ratio->calset_2, base + MIPHY_PLL_CALSET_2);
+ writeb_relaxed(pll_ratio->calset_3, base + MIPHY_PLL_CALSET_3);
+ writeb_relaxed(pll_ratio->calset_4, base + MIPHY_PLL_CALSET_4);
+ writeb_relaxed(pll_ratio->cal_ctrl, base + MIPHY_PLL_CALSET_CTRL);
+
+ writeb_relaxed(TX_SEL, base + MIPHY_BOUNDARY_SEL);
+
+ val = (0x68 << 1) | TX_SLEW_CAL_MAN_EN;
+ writeb_relaxed(val, base + MIPHY_TX_CAL_MAN);
+
+ val = VGA_OFFSET_POLARITY | CAL_OFFSET_THRESHOLD_64 | CAL_OFFSET_VGA_64;
+
+ if (miphy_phy->type != PHY_TYPE_SATA)
+ val |= OFFSET_COMPENSATION_EN;
+
+ writeb_relaxed(val, base + MIPHY_RX_CAL_OFFSET_CTRL);
+
+ if (miphy_phy->type == PHY_TYPE_USB3) {
+ writeb_relaxed(0x00, base + MIPHY_CONF);
+ writeb_relaxed(0x70, base + MIPHY_RX_LOCK_STEP);
+ writeb_relaxed(EN_FIRST_HALF, base + MIPHY_RX_SIGDET_SLEEP_OA);
+ writeb_relaxed(EN_FIRST_HALF, base + MIPHY_RX_SIGDET_SLEEP_SEL);
+ writeb_relaxed(EN_FIRST_HALF, base + MIPHY_RX_SIGDET_WAIT_SEL);
+
+ val = EN_DIGIT_SIGNAL_CHECK | EN_FIRST_HALF;
+ writeb_relaxed(val, base + MIPHY_RX_SIGDET_DATA_SEL);
+ }
+
+}
+
+static inline void miphy28lp_sata_config_gen(struct miphy28lp_phy *miphy_phy)
+{
+ void __iomem *base = miphy_phy->base;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(sata_pll_gen); i++) {
+ struct miphy28lp_pll_gen *gen = &sata_pll_gen[i];
+
+ /* Banked settings */
+ writeb_relaxed(gen->bank, base + MIPHY_CONF);
+ writeb_relaxed(gen->speed, base + MIPHY_SPEED);
+ writeb_relaxed(gen->bias_boost_1, base + MIPHY_BIAS_BOOST_1);
+ writeb_relaxed(gen->bias_boost_2, base + MIPHY_BIAS_BOOST_2);
+
+ /* TX buffer Settings */
+ writeb_relaxed(gen->tx_ctrl_2, base + MIPHY_TX_CTRL_2);
+ writeb_relaxed(gen->tx_ctrl_3, base + MIPHY_TX_CTRL_3);
+
+ /* RX Buffer Settings */
+ writeb_relaxed(gen->rx_buff_ctrl, base + MIPHY_RX_BUFFER_CTRL);
+ writeb_relaxed(gen->rx_vga_gain, base + MIPHY_RX_VGA_GAIN);
+ writeb_relaxed(gen->rx_equ_gain_1, base + MIPHY_RX_EQU_GAIN_1);
+ writeb_relaxed(gen->rx_equ_gain_2, base + MIPHY_RX_EQU_GAIN_2);
+ writeb_relaxed(gen->rx_equ_gain_3, base + MIPHY_RX_EQU_GAIN_3);
+ }
+}
+
+static inline void miphy28lp_pcie_config_gen(struct miphy28lp_phy *miphy_phy)
+{
+ void __iomem *base = miphy_phy->base;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(pcie_pll_gen); i++) {
+ struct miphy28lp_pll_gen *gen = &pcie_pll_gen[i];
+
+ /* Banked settings */
+ writeb_relaxed(gen->bank, base + MIPHY_CONF);
+ writeb_relaxed(gen->speed, base + MIPHY_SPEED);
+ writeb_relaxed(gen->bias_boost_1, base + MIPHY_BIAS_BOOST_1);
+ writeb_relaxed(gen->bias_boost_2, base + MIPHY_BIAS_BOOST_2);
+
+ /* TX buffer Settings */
+ writeb_relaxed(gen->tx_ctrl_1, base + MIPHY_TX_CTRL_1);
+ writeb_relaxed(gen->tx_ctrl_2, base + MIPHY_TX_CTRL_2);
+ writeb_relaxed(gen->tx_ctrl_3, base + MIPHY_TX_CTRL_3);
+
+ writeb_relaxed(gen->rx_k_gain, base + MIPHY_RX_K_GAIN);
+
+ /* RX Buffer Settings */
+ writeb_relaxed(gen->rx_buff_ctrl, base + MIPHY_RX_BUFFER_CTRL);
+ writeb_relaxed(gen->rx_vga_gain, base + MIPHY_RX_VGA_GAIN);
+ writeb_relaxed(gen->rx_equ_gain_1, base + MIPHY_RX_EQU_GAIN_1);
+ writeb_relaxed(gen->rx_equ_gain_2, base + MIPHY_RX_EQU_GAIN_2);
+ }
+}
+
+static inline int miphy28lp_wait_compensation(struct miphy28lp_phy *miphy_phy)
+{
+ u8 val;
+
+ /* Waiting for Compensation to complete */
+ return readb_relaxed_poll_timeout(miphy_phy->base + MIPHY_COMP_FSM_6,
+ val, val & COMP_DONE, 1, 5 * USEC_PER_SEC);
+}
+
+
+static inline int miphy28lp_compensation(struct miphy28lp_phy *miphy_phy,
+ struct pll_ratio *pll_ratio)
+{
+ void __iomem *base = miphy_phy->base;
+
+ /* Poll for HFC ready after reset release */
+ /* Compensation measurement */
+ writeb_relaxed(RST_PLL_SW | RST_COMP_SW, base + MIPHY_RESET);
+
+ writeb_relaxed(0x00, base + MIPHY_PLL_COMMON_MISC_2);
+ writeb_relaxed(pll_ratio->clk_ref, base + MIPHY_PLL_CLKREF_FREQ);
+ writeb_relaxed(COMP_START, base + MIPHY_COMP_FSM_1);
+
+ if (miphy_phy->type == PHY_TYPE_PCIE)
+ writeb_relaxed(RST_PLL_SW, base + MIPHY_RESET);
+
+ writeb_relaxed(0x00, base + MIPHY_RESET);
+ writeb_relaxed(START_ACT_FILT, base + MIPHY_PLL_COMMON_MISC_2);
+ writeb_relaxed(SET_NEW_CHANGE, base + MIPHY_PLL_SBR_1);
+
+ /* TX compensation offset to re-center TX impedance */
+ writeb_relaxed(0x00, base + MIPHY_COMP_POSTP);
+
+ if (miphy_phy->type == PHY_TYPE_PCIE)
+ return miphy28lp_wait_compensation(miphy_phy);
+
+ return 0;
+}
+
+static inline void miphy28_usb3_miphy_reset(struct miphy28lp_phy *miphy_phy)
+{
+ void __iomem *base = miphy_phy->base;
+ u8 val;
+
+ /* MIPHY Reset */
+ writeb_relaxed(RST_APPLI_SW, base + MIPHY_CONF_RESET);
+ writeb_relaxed(0x00, base + MIPHY_CONF_RESET);
+ writeb_relaxed(RST_COMP_SW, base + MIPHY_RESET);
+
+ val = RST_COMP_SW | RST_PLL_SW;
+ writeb_relaxed(val, base + MIPHY_RESET);
+
+ writeb_relaxed(0x00, base + MIPHY_PLL_COMMON_MISC_2);
+ writeb_relaxed(0x1e, base + MIPHY_PLL_CLKREF_FREQ);
+ writeb_relaxed(COMP_START, base + MIPHY_COMP_FSM_1);
+ writeb_relaxed(RST_PLL_SW, base + MIPHY_RESET);
+ writeb_relaxed(0x00, base + MIPHY_RESET);
+ writeb_relaxed(START_ACT_FILT, base + MIPHY_PLL_COMMON_MISC_2);
+ writeb_relaxed(0x00, base + MIPHY_CONF);
+ writeb_relaxed(0x00, base + MIPHY_BOUNDARY_1);
+ writeb_relaxed(0x00, base + MIPHY_TST_BIAS_BOOST_2);
+ writeb_relaxed(0x00, base + MIPHY_CONF);
+ writeb_relaxed(SET_NEW_CHANGE, base + MIPHY_PLL_SBR_1);
+ writeb_relaxed(0xa5, base + MIPHY_DEBUG_BUS);
+ writeb_relaxed(0x00, base + MIPHY_CONF);
+}
+
+static void miphy_sata_tune_ssc(struct miphy28lp_phy *miphy_phy)
+{
+ void __iomem *base = miphy_phy->base;
+ u8 val;
+
+ /* Compensate Tx impedance to avoid out of range values */
+ /*
+ * Enable the SSC on PLL for all banks
+ * SSC Modulation @ 31 KHz and 4000 ppm modulation amp
+ */
+ val = readb_relaxed(base + MIPHY_BOUNDARY_2);
+ val |= SSC_EN_SW;
+ writeb_relaxed(val, base + MIPHY_BOUNDARY_2);
+
+ val = readb_relaxed(base + MIPHY_BOUNDARY_SEL);
+ val |= SSC_SEL;
+ writeb_relaxed(val, base + MIPHY_BOUNDARY_SEL);
+
+ for (val = 0; val < MIPHY_SATA_BANK_NB; val++) {
+ writeb_relaxed(val, base + MIPHY_CONF);
+
+ /* Add value to each reference clock cycle */
+ /* and define the period length of the SSC */
+ writeb_relaxed(0x3c, base + MIPHY_PLL_SBR_2);
+ writeb_relaxed(0x6c, base + MIPHY_PLL_SBR_3);
+ writeb_relaxed(0x81, base + MIPHY_PLL_SBR_4);
+
+ /* Clear any previous request */
+ writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1);
+
+ /* requests the PLL to take in account new parameters */
+ writeb_relaxed(SET_NEW_CHANGE, base + MIPHY_PLL_SBR_1);
+
+ /* To be sure there is no other pending requests */
+ writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1);
+ }
+}
+
+static void miphy_pcie_tune_ssc(struct miphy28lp_phy *miphy_phy)
+{
+ void __iomem *base = miphy_phy->base;
+ u8 val;
+
+ /* Compensate Tx impedance to avoid out of range values */
+ /*
+ * Enable the SSC on PLL for all banks
+ * SSC Modulation @ 31 KHz and 4000 ppm modulation amp
+ */
+ val = readb_relaxed(base + MIPHY_BOUNDARY_2);
+ val |= SSC_EN_SW;
+ writeb_relaxed(val, base + MIPHY_BOUNDARY_2);
+
+ val = readb_relaxed(base + MIPHY_BOUNDARY_SEL);
+ val |= SSC_SEL;
+ writeb_relaxed(val, base + MIPHY_BOUNDARY_SEL);
+
+ for (val = 0; val < MIPHY_PCIE_BANK_NB; val++) {
+ writeb_relaxed(val, base + MIPHY_CONF);
+
+ /* Validate Step component */
+ writeb_relaxed(0x69, base + MIPHY_PLL_SBR_3);
+ writeb_relaxed(0x21, base + MIPHY_PLL_SBR_4);
+
+ /* Validate Period component */
+ writeb_relaxed(0x3c, base + MIPHY_PLL_SBR_2);
+ writeb_relaxed(0x21, base + MIPHY_PLL_SBR_4);
+
+ /* Clear any previous request */
+ writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1);
+
+ /* requests the PLL to take in account new parameters */
+ writeb_relaxed(SET_NEW_CHANGE, base + MIPHY_PLL_SBR_1);
+
+ /* To be sure there is no other pending requests */
+ writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1);
+ }
+}
+
+static inline void miphy_tune_tx_impedance(struct miphy28lp_phy *miphy_phy)
+{
+ /* Compensate Tx impedance to avoid out of range values */
+ writeb_relaxed(0x02, miphy_phy->base + MIPHY_COMP_POSTP);
+}
+
+static inline int miphy28lp_configure_sata(struct miphy28lp_phy *miphy_phy)
+{
+ void __iomem *base = miphy_phy->base;
+ int err;
+ u8 val;
+
+ /* Putting Macro in reset */
+ miphy28lp_set_reset(miphy_phy);
+
+ /* PLL calibration */
+ miphy28lp_pll_calibration(miphy_phy, &sata_pll_ratio);
+
+ /* Banked settings Gen1/Gen2/Gen3 */
+ miphy28lp_sata_config_gen(miphy_phy);
+
+ /* Power control */
+ /* Input bridge enable, manual input bridge control */
+ writeb_relaxed(0x21, base + MIPHY_RX_POWER_CTRL_1);
+
+ /* Macro out of reset */
+ writeb_relaxed(0x00, base + MIPHY_CONF_RESET);
+
+ /* Poll for HFC ready after reset release */
+ /* Compensation measurement */
+ err = miphy28lp_compensation(miphy_phy, &sata_pll_ratio);
+ if (err)
+ return err;
+
+ if (miphy_phy->px_rx_pol_inv) {
+ /* Invert Rx polarity */
+ val = readb_relaxed(miphy_phy->base + MIPHY_CONTROL);
+ val |= PX_RX_POL;
+ writeb_relaxed(val, miphy_phy->base + MIPHY_CONTROL);
+ }
+
+ if (miphy_phy->ssc)
+ miphy_sata_tune_ssc(miphy_phy);
+
+ if (miphy_phy->tx_impedance)
+ miphy_tune_tx_impedance(miphy_phy);
+
+ return 0;
+}
+
+static inline int miphy28lp_configure_pcie(struct miphy28lp_phy *miphy_phy)
+{
+ void __iomem *base = miphy_phy->base;
+ int err;
+
+ /* Putting Macro in reset */
+ miphy28lp_set_reset(miphy_phy);
+
+ /* PLL calibration */
+ miphy28lp_pll_calibration(miphy_phy, &pcie_pll_ratio);
+
+ /* Banked settings Gen1/Gen2 */
+ miphy28lp_pcie_config_gen(miphy_phy);
+
+ /* Power control */
+ /* Input bridge enable, manual input bridge control */
+ writeb_relaxed(0x21, base + MIPHY_RX_POWER_CTRL_1);
+
+ /* Macro out of reset */
+ writeb_relaxed(0x00, base + MIPHY_CONF_RESET);
+
+ /* Poll for HFC ready after reset release */
+ /* Compensation measurement */
+ err = miphy28lp_compensation(miphy_phy, &pcie_pll_ratio);
+ if (err)
+ return err;
+
+ if (miphy_phy->ssc)
+ miphy_pcie_tune_ssc(miphy_phy);
+
+ if (miphy_phy->tx_impedance)
+ miphy_tune_tx_impedance(miphy_phy);
+
+ return 0;
+}
+
+
+static inline void miphy28lp_configure_usb3(struct miphy28lp_phy *miphy_phy)
+{
+ void __iomem *base = miphy_phy->base;
+ u8 val;
+
+ /* Putting Macro in reset */
+ miphy28lp_set_reset(miphy_phy);
+
+ /* PLL calibration */
+ miphy28lp_pll_calibration(miphy_phy, &usb3_pll_ratio);
+
+ /* Writing The Speed Rate */
+ writeb_relaxed(0x00, base + MIPHY_CONF);
+
+ val = RX_SPDSEL_20DEC | TX_SPDSEL_20DEC;
+ writeb_relaxed(val, base + MIPHY_SPEED);
+
+ /* RX Channel compensation and calibration */
+ writeb_relaxed(0x1c, base + MIPHY_RX_LOCK_SETTINGS_OPT);
+ writeb_relaxed(0x51, base + MIPHY_RX_CAL_CTRL_1);
+ writeb_relaxed(0x70, base + MIPHY_RX_CAL_CTRL_2);
+
+ val = OFFSET_COMPENSATION_EN | VGA_OFFSET_POLARITY |
+ CAL_OFFSET_THRESHOLD_64 | CAL_OFFSET_VGA_64;
+ writeb_relaxed(val, base + MIPHY_RX_CAL_OFFSET_CTRL);
+ writeb_relaxed(0x22, base + MIPHY_RX_CAL_VGA_STEP);
+ writeb_relaxed(0x0e, base + MIPHY_RX_CAL_OPT_LENGTH);
+
+ val = EQ_DC_GAIN | VGA_GAIN;
+ writeb_relaxed(val, base + MIPHY_RX_BUFFER_CTRL);
+ writeb_relaxed(0x78, base + MIPHY_RX_EQU_GAIN_1);
+ writeb_relaxed(0x1b, base + MIPHY_SYNCHAR_CONTROL);
+
+ /* TX compensation offset to re-center TX impedance */
+ writeb_relaxed(0x02, base + MIPHY_COMP_POSTP);
+
+ /* Enable GENSEL_SEL and SSC */
+ /* TX_SEL=0 swing preemp forced by pipe registres */
+ val = SSC_SEL | GENSEL_SEL;
+ writeb_relaxed(val, base + MIPHY_BOUNDARY_SEL);
+
+ /* MIPHY Bias boost */
+ writeb_relaxed(0x00, base + MIPHY_BIAS_BOOST_1);
+ writeb_relaxed(0xa7, base + MIPHY_BIAS_BOOST_2);
+
+ /* SSC modulation */
+ writeb_relaxed(SSC_EN_SW, base + MIPHY_BOUNDARY_2);
+
+ /* MIPHY TX control */
+ writeb_relaxed(0x00, base + MIPHY_CONF);
+
+ /* Validate Step component */
+ writeb_relaxed(0x5a, base + MIPHY_PLL_SBR_3);
+ writeb_relaxed(0xa0, base + MIPHY_PLL_SBR_4);
+
+ /* Validate Period component */
+ writeb_relaxed(0x3c, base + MIPHY_PLL_SBR_2);
+ writeb_relaxed(0xa1, base + MIPHY_PLL_SBR_4);
+
+ /* Clear any previous request */
+ writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1);
+
+ /* requests the PLL to take in account new parameters */
+ writeb_relaxed(0x02, base + MIPHY_PLL_SBR_1);
+
+ /* To be sure there is no other pending requests */
+ writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1);
+
+ /* Rx PI controller settings */
+ writeb_relaxed(0xca, base + MIPHY_RX_K_GAIN);
+
+ /* MIPHY RX input bridge control */
+ /* INPUT_BRIDGE_EN_SW=1, manual input bridge control[0]=1 */
+ writeb_relaxed(0x21, base + MIPHY_RX_POWER_CTRL_1);
+ writeb_relaxed(0x29, base + MIPHY_RX_POWER_CTRL_1);
+ writeb_relaxed(0x1a, base + MIPHY_RX_POWER_CTRL_2);
+
+ /* MIPHY Reset for usb3 */
+ miphy28_usb3_miphy_reset(miphy_phy);
+}
+
+static inline int miphy_is_ready(struct miphy28lp_phy *miphy_phy)
+{
+ u8 mask = HFC_PLL | HFC_RDY;
+ u8 val;
+
+ /*
+ * For PCIe and USB3 check only that PLL and HFC are ready
+ * For SATA check also that phy is ready!
+ */
+ if (miphy_phy->type == PHY_TYPE_SATA)
+ mask |= PHY_RDY;
+
+ return readb_relaxed_poll_timeout(miphy_phy->base + MIPHY_STATUS_1,
+ val, (val & mask) == mask, 1,
+ 5 * USEC_PER_SEC);
+}
+
+static int miphy_osc_is_ready(struct miphy28lp_phy *miphy_phy)
+{
+ struct miphy28lp_dev *miphy_dev = miphy_phy->phydev;
+ u32 val;
+
+ if (!miphy_phy->osc_rdy)
+ return 0;
+
+ if (!miphy_phy->syscfg_reg[SYSCFG_STATUS])
+ return -EINVAL;
+
+ return regmap_read_poll_timeout(miphy_dev->regmap,
+ miphy_phy->syscfg_reg[SYSCFG_STATUS],
+ val, val & MIPHY_OSC_RDY, 1,
+ 5 * USEC_PER_SEC);
+}
+
+static int miphy28lp_get_resource_byname(struct device_node *child,
+ char *rname, struct resource *res)
+{
+ int index;
+
+ index = of_property_match_string(child, "reg-names", rname);
+ if (index < 0)
+ return -ENODEV;
+
+ return of_address_to_resource(child, index, res);
+}
+
+static int miphy28lp_get_one_addr(struct device *dev,
+ struct device_node *child, char *rname,
+ void __iomem **base)
+{
+ struct resource res;
+ int ret;
+
+ ret = miphy28lp_get_resource_byname(child, rname, &res);
+ if (!ret) {
+ *base = devm_ioremap(dev, res.start, resource_size(&res));
+ if (!*base) {
+ dev_err(dev, "failed to ioremap %s address region\n"
+ , rname);
+ return -ENOENT;
+ }
+ }
+
+ return 0;
+}
+
+/* MiPHY reset and sysconf setup */
+static int miphy28lp_setup(struct miphy28lp_phy *miphy_phy, u32 miphy_val)
+{
+ int err;
+ struct miphy28lp_dev *miphy_dev = miphy_phy->phydev;
+
+ if (!miphy_phy->syscfg_reg[SYSCFG_CTRL])
+ return -EINVAL;
+
+ err = reset_control_assert(miphy_phy->miphy_rst);
+ if (err) {
+ dev_err(miphy_dev->dev, "unable to bring out of miphy reset\n");
+ return err;
+ }
+
+ if (miphy_phy->osc_force_ext)
+ miphy_val |= MIPHY_OSC_FORCE_EXT;
+
+ regmap_update_bits(miphy_dev->regmap,
+ miphy_phy->syscfg_reg[SYSCFG_CTRL],
+ MIPHY_CTRL_MASK, miphy_val);
+
+ err = reset_control_deassert(miphy_phy->miphy_rst);
+ if (err) {
+ dev_err(miphy_dev->dev, "unable to bring out of miphy reset\n");
+ return err;
+ }
+
+ return miphy_osc_is_ready(miphy_phy);
+}
+
+static int miphy28lp_init_sata(struct miphy28lp_phy *miphy_phy)
+{
+ struct miphy28lp_dev *miphy_dev = miphy_phy->phydev;
+ int err, sata_conf = SATA_CTRL_SELECT_SATA;
+
+ if ((!miphy_phy->syscfg_reg[SYSCFG_SATA]) ||
+ (!miphy_phy->syscfg_reg[SYSCFG_PCI]) ||
+ (!miphy_phy->base))
+ return -EINVAL;
+
+ dev_info(miphy_dev->dev, "sata-up mode, addr 0x%p\n", miphy_phy->base);
+
+ /* Configure the glue-logic */
+ sata_conf |= ((miphy_phy->sata_gen - SATA_GEN1) << SATA_SPDMODE);
+
+ regmap_update_bits(miphy_dev->regmap,
+ miphy_phy->syscfg_reg[SYSCFG_SATA],
+ SATA_CTRL_MASK, sata_conf);
+
+ regmap_update_bits(miphy_dev->regmap, miphy_phy->syscfg_reg[SYSCFG_PCI],
+ PCIE_CTRL_MASK, SATA_CTRL_SELECT_PCIE);
+
+ /* MiPHY path and clocking init */
+ err = miphy28lp_setup(miphy_phy, MIPHY_CTRL_DEFAULT);
+
+ if (err) {
+ dev_err(miphy_dev->dev, "SATA phy setup failed\n");
+ return err;
+ }
+
+ /* initialize miphy */
+ miphy28lp_configure_sata(miphy_phy);
+
+ return miphy_is_ready(miphy_phy);
+}
+
+static int miphy28lp_init_pcie(struct miphy28lp_phy *miphy_phy)
+{
+ struct miphy28lp_dev *miphy_dev = miphy_phy->phydev;
+ int err;
+
+ if ((!miphy_phy->syscfg_reg[SYSCFG_SATA]) ||
+ (!miphy_phy->syscfg_reg[SYSCFG_PCI])
+ || (!miphy_phy->base) || (!miphy_phy->pipebase))
+ return -EINVAL;
+
+ dev_info(miphy_dev->dev, "pcie-up mode, addr 0x%p\n", miphy_phy->base);
+
+ /* Configure the glue-logic */
+ regmap_update_bits(miphy_dev->regmap,
+ miphy_phy->syscfg_reg[SYSCFG_SATA],
+ SATA_CTRL_MASK, SATA_CTRL_SELECT_PCIE);
+
+ regmap_update_bits(miphy_dev->regmap, miphy_phy->syscfg_reg[SYSCFG_PCI],
+ PCIE_CTRL_MASK, SYSCFG_PCIE_PCIE_VAL);
+
+ /* MiPHY path and clocking init */
+ err = miphy28lp_setup(miphy_phy, MIPHY_CTRL_DEFAULT);
+
+ if (err) {
+ dev_err(miphy_dev->dev, "PCIe phy setup failed\n");
+ return err;
+ }
+
+ /* initialize miphy */
+ err = miphy28lp_configure_pcie(miphy_phy);
+ if (err)
+ return err;
+
+ /* PIPE Wrapper Configuration */
+ writeb_relaxed(0x68, miphy_phy->pipebase + 0x104); /* Rise_0 */
+ writeb_relaxed(0x61, miphy_phy->pipebase + 0x105); /* Rise_1 */
+ writeb_relaxed(0x68, miphy_phy->pipebase + 0x108); /* Fall_0 */
+ writeb_relaxed(0x61, miphy_phy->pipebase + 0x109); /* Fall-1 */
+ writeb_relaxed(0x68, miphy_phy->pipebase + 0x10c); /* Threshold_0 */
+ writeb_relaxed(0x60, miphy_phy->pipebase + 0x10d); /* Threshold_1 */
+
+ /* Wait for phy_ready */
+ return miphy_is_ready(miphy_phy);
+}
+
+static int miphy28lp_init_usb3(struct miphy28lp_phy *miphy_phy)
+{
+ struct miphy28lp_dev *miphy_dev = miphy_phy->phydev;
+ int err;
+
+ if ((!miphy_phy->base) || (!miphy_phy->pipebase))
+ return -EINVAL;
+
+ dev_info(miphy_dev->dev, "usb3-up mode, addr 0x%p\n", miphy_phy->base);
+
+ /* MiPHY path and clocking init */
+ err = miphy28lp_setup(miphy_phy, MIPHY_CTRL_SYNC_D_EN);
+ if (err) {
+ dev_err(miphy_dev->dev, "USB3 phy setup failed\n");
+ return err;
+ }
+
+ /* initialize miphy */
+ miphy28lp_configure_usb3(miphy_phy);
+
+ /* PIPE Wrapper Configuration */
+ writeb_relaxed(0x68, miphy_phy->pipebase + 0x23);
+ writeb_relaxed(0x61, miphy_phy->pipebase + 0x24);
+ writeb_relaxed(0x68, miphy_phy->pipebase + 0x26);
+ writeb_relaxed(0x61, miphy_phy->pipebase + 0x27);
+ writeb_relaxed(0x18, miphy_phy->pipebase + 0x29);
+ writeb_relaxed(0x61, miphy_phy->pipebase + 0x2a);
+
+ /* pipe Wrapper usb3 TX swing de-emph margin PREEMPH[7:4], SWING[3:0] */
+ writeb_relaxed(0X67, miphy_phy->pipebase + 0x68);
+ writeb_relaxed(0x0d, miphy_phy->pipebase + 0x69);
+ writeb_relaxed(0X67, miphy_phy->pipebase + 0x6a);
+ writeb_relaxed(0X0d, miphy_phy->pipebase + 0x6b);
+ writeb_relaxed(0X67, miphy_phy->pipebase + 0x6c);
+ writeb_relaxed(0X0d, miphy_phy->pipebase + 0x6d);
+ writeb_relaxed(0X67, miphy_phy->pipebase + 0x6e);
+ writeb_relaxed(0X0d, miphy_phy->pipebase + 0x6f);
+
+ return miphy_is_ready(miphy_phy);
+}
+
+static int miphy28lp_init(struct phy *phy)
+{
+ struct miphy28lp_phy *miphy_phy = phy_get_drvdata(phy);
+ struct miphy28lp_dev *miphy_dev = miphy_phy->phydev;
+ int ret;
+
+ mutex_lock(&miphy_dev->miphy_mutex);
+
+ switch (miphy_phy->type) {
+
+ case PHY_TYPE_SATA:
+ ret = miphy28lp_init_sata(miphy_phy);
+ break;
+ case PHY_TYPE_PCIE:
+ ret = miphy28lp_init_pcie(miphy_phy);
+ break;
+ case PHY_TYPE_USB3:
+ ret = miphy28lp_init_usb3(miphy_phy);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ mutex_unlock(&miphy_dev->miphy_mutex);
+
+ return ret;
+}
+
+static int miphy28lp_get_addr(struct miphy28lp_phy *miphy_phy)
+{
+ struct miphy28lp_dev *miphy_dev = miphy_phy->phydev;
+ struct device_node *phynode = miphy_phy->phy->dev.of_node;
+ int err;
+
+ if ((miphy_phy->type != PHY_TYPE_SATA) &&
+ (miphy_phy->type != PHY_TYPE_PCIE) &&
+ (miphy_phy->type != PHY_TYPE_USB3)) {
+ return -EINVAL;
+ }
+
+ err = miphy28lp_get_one_addr(miphy_dev->dev, phynode,
+ PHY_TYPE_name[miphy_phy->type - PHY_TYPE_SATA],
+ &miphy_phy->base);
+ if (err)
+ return err;
+
+ if ((miphy_phy->type == PHY_TYPE_PCIE) ||
+ (miphy_phy->type == PHY_TYPE_USB3)) {
+ err = miphy28lp_get_one_addr(miphy_dev->dev, phynode, "pipew",
+ &miphy_phy->pipebase);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static struct phy *miphy28lp_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct miphy28lp_dev *miphy_dev = dev_get_drvdata(dev);
+ struct miphy28lp_phy *miphy_phy = NULL;
+ struct device_node *phynode = args->np;
+ int ret, index = 0;
+
+ if (args->args_count != 1) {
+ dev_err(dev, "Invalid number of cells in 'phy' property\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ for (index = 0; index < miphy_dev->nphys; index++)
+ if (phynode == miphy_dev->phys[index]->phy->dev.of_node) {
+ miphy_phy = miphy_dev->phys[index];
+ break;
+ }
+
+ if (!miphy_phy) {
+ dev_err(dev, "Failed to find appropriate phy\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ miphy_phy->type = args->args[0];
+
+ ret = miphy28lp_get_addr(miphy_phy);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ return miphy_phy->phy;
+}
+
+static const struct phy_ops miphy28lp_ops = {
+ .init = miphy28lp_init,
+ .owner = THIS_MODULE,
+};
+
+static int miphy28lp_probe_resets(struct device_node *node,
+ struct miphy28lp_phy *miphy_phy)
+{
+ struct miphy28lp_dev *miphy_dev = miphy_phy->phydev;
+ int err;
+
+ miphy_phy->miphy_rst =
+ of_reset_control_get_shared(node, "miphy-sw-rst");
+
+ if (IS_ERR(miphy_phy->miphy_rst)) {
+ dev_err(miphy_dev->dev,
+ "miphy soft reset control not defined\n");
+ return PTR_ERR(miphy_phy->miphy_rst);
+ }
+
+ err = reset_control_deassert(miphy_phy->miphy_rst);
+ if (err) {
+ dev_err(miphy_dev->dev, "unable to bring out of miphy reset\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static int miphy28lp_of_probe(struct device_node *np,
+ struct miphy28lp_phy *miphy_phy)
+{
+ int i;
+ u32 ctrlreg;
+
+ miphy_phy->osc_force_ext =
+ of_property_read_bool(np, "st,osc-force-ext");
+
+ miphy_phy->osc_rdy = of_property_read_bool(np, "st,osc-rdy");
+
+ miphy_phy->px_rx_pol_inv =
+ of_property_read_bool(np, "st,px_rx_pol_inv");
+
+ miphy_phy->ssc = of_property_read_bool(np, "st,ssc-on");
+
+ miphy_phy->tx_impedance =
+ of_property_read_bool(np, "st,tx-impedance-comp");
+
+ of_property_read_u32(np, "st,sata-gen", &miphy_phy->sata_gen);
+ if (!miphy_phy->sata_gen)
+ miphy_phy->sata_gen = SATA_GEN1;
+
+ for (i = 0; i < SYSCFG_REG_MAX; i++) {
+ if (!of_property_read_u32_index(np, "st,syscfg", i, &ctrlreg))
+ miphy_phy->syscfg_reg[i] = ctrlreg;
+ }
+
+ return 0;
+}
+
+static int miphy28lp_probe(struct platform_device *pdev)
+{
+ struct device_node *child, *np = pdev->dev.of_node;
+ struct miphy28lp_dev *miphy_dev;
+ struct phy_provider *provider;
+ struct phy *phy;
+ int ret, port = 0;
+
+ miphy_dev = devm_kzalloc(&pdev->dev, sizeof(*miphy_dev), GFP_KERNEL);
+ if (!miphy_dev)
+ return -ENOMEM;
+
+ miphy_dev->nphys = of_get_child_count(np);
+ miphy_dev->phys = devm_kcalloc(&pdev->dev, miphy_dev->nphys,
+ sizeof(*miphy_dev->phys), GFP_KERNEL);
+ if (!miphy_dev->phys)
+ return -ENOMEM;
+
+ miphy_dev->regmap = syscon_regmap_lookup_by_phandle(np, "st,syscfg");
+ if (IS_ERR(miphy_dev->regmap)) {
+ dev_err(miphy_dev->dev, "No syscfg phandle specified\n");
+ return PTR_ERR(miphy_dev->regmap);
+ }
+
+ miphy_dev->dev = &pdev->dev;
+
+ dev_set_drvdata(&pdev->dev, miphy_dev);
+
+ mutex_init(&miphy_dev->miphy_mutex);
+
+ for_each_child_of_node(np, child) {
+ struct miphy28lp_phy *miphy_phy;
+
+ miphy_phy = devm_kzalloc(&pdev->dev, sizeof(*miphy_phy),
+ GFP_KERNEL);
+ if (!miphy_phy) {
+ ret = -ENOMEM;
+ goto put_child;
+ }
+
+ miphy_dev->phys[port] = miphy_phy;
+
+ phy = devm_phy_create(&pdev->dev, child, &miphy28lp_ops);
+ if (IS_ERR(phy)) {
+ dev_err(&pdev->dev, "failed to create PHY\n");
+ ret = PTR_ERR(phy);
+ goto put_child;
+ }
+
+ miphy_dev->phys[port]->phy = phy;
+ miphy_dev->phys[port]->phydev = miphy_dev;
+
+ ret = miphy28lp_of_probe(child, miphy_phy);
+ if (ret)
+ goto put_child;
+
+ ret = miphy28lp_probe_resets(child, miphy_dev->phys[port]);
+ if (ret)
+ goto put_child;
+
+ phy_set_drvdata(phy, miphy_dev->phys[port]);
+ port++;
+
+ }
+
+ provider = devm_of_phy_provider_register(&pdev->dev, miphy28lp_xlate);
+ return PTR_ERR_OR_ZERO(provider);
+put_child:
+ of_node_put(child);
+ return ret;
+}
+
+static const struct of_device_id miphy28lp_of_match[] = {
+ {.compatible = "st,miphy28lp-phy", },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, miphy28lp_of_match);
+
+static struct platform_driver miphy28lp_driver = {
+ .probe = miphy28lp_probe,
+ .driver = {
+ .name = "miphy28lp-phy",
+ .of_match_table = miphy28lp_of_match,
+ }
+};
+
+module_platform_driver(miphy28lp_driver);
+
+MODULE_AUTHOR("Alexandre Torgue <alexandre.torgue@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics miphy28lp driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/st/phy-spear1310-miphy.c b/drivers/phy/st/phy-spear1310-miphy.c
new file mode 100644
index 0000000000..35a9831b51
--- /dev/null
+++ b/drivers/phy/st/phy-spear1310-miphy.c
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ST SPEAr1310-miphy driver
+ *
+ * Copyright (C) 2014 ST Microelectronics
+ * Pratyush Anand <pratyush.anand@gmail.com>
+ * Mohit Kumar <mohit.kumar.dhaka@gmail.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/kernel.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/regmap.h>
+
+/* SPEAr1310 Registers */
+#define SPEAR1310_PCIE_SATA_CFG 0x3A4
+ #define SPEAR1310_PCIE_SATA2_SEL_PCIE (0 << 31)
+ #define SPEAR1310_PCIE_SATA1_SEL_PCIE (0 << 30)
+ #define SPEAR1310_PCIE_SATA0_SEL_PCIE (0 << 29)
+ #define SPEAR1310_PCIE_SATA2_SEL_SATA BIT(31)
+ #define SPEAR1310_PCIE_SATA1_SEL_SATA BIT(30)
+ #define SPEAR1310_PCIE_SATA0_SEL_SATA BIT(29)
+ #define SPEAR1310_SATA2_CFG_TX_CLK_EN BIT(27)
+ #define SPEAR1310_SATA2_CFG_RX_CLK_EN BIT(26)
+ #define SPEAR1310_SATA2_CFG_POWERUP_RESET BIT(25)
+ #define SPEAR1310_SATA2_CFG_PM_CLK_EN BIT(24)
+ #define SPEAR1310_SATA1_CFG_TX_CLK_EN BIT(23)
+ #define SPEAR1310_SATA1_CFG_RX_CLK_EN BIT(22)
+ #define SPEAR1310_SATA1_CFG_POWERUP_RESET BIT(21)
+ #define SPEAR1310_SATA1_CFG_PM_CLK_EN BIT(20)
+ #define SPEAR1310_SATA0_CFG_TX_CLK_EN BIT(19)
+ #define SPEAR1310_SATA0_CFG_RX_CLK_EN BIT(18)
+ #define SPEAR1310_SATA0_CFG_POWERUP_RESET BIT(17)
+ #define SPEAR1310_SATA0_CFG_PM_CLK_EN BIT(16)
+ #define SPEAR1310_PCIE2_CFG_DEVICE_PRESENT BIT(11)
+ #define SPEAR1310_PCIE2_CFG_POWERUP_RESET BIT(10)
+ #define SPEAR1310_PCIE2_CFG_CORE_CLK_EN BIT(9)
+ #define SPEAR1310_PCIE2_CFG_AUX_CLK_EN BIT(8)
+ #define SPEAR1310_PCIE1_CFG_DEVICE_PRESENT BIT(7)
+ #define SPEAR1310_PCIE1_CFG_POWERUP_RESET BIT(6)
+ #define SPEAR1310_PCIE1_CFG_CORE_CLK_EN BIT(5)
+ #define SPEAR1310_PCIE1_CFG_AUX_CLK_EN BIT(4)
+ #define SPEAR1310_PCIE0_CFG_DEVICE_PRESENT BIT(3)
+ #define SPEAR1310_PCIE0_CFG_POWERUP_RESET BIT(2)
+ #define SPEAR1310_PCIE0_CFG_CORE_CLK_EN BIT(1)
+ #define SPEAR1310_PCIE0_CFG_AUX_CLK_EN BIT(0)
+
+ #define SPEAR1310_PCIE_CFG_MASK(x) ((0xF << (x * 4)) | BIT((x + 29)))
+ #define SPEAR1310_SATA_CFG_MASK(x) ((0xF << (x * 4 + 16)) | \
+ BIT((x + 29)))
+ #define SPEAR1310_PCIE_CFG_VAL(x) \
+ (SPEAR1310_PCIE_SATA##x##_SEL_PCIE | \
+ SPEAR1310_PCIE##x##_CFG_AUX_CLK_EN | \
+ SPEAR1310_PCIE##x##_CFG_CORE_CLK_EN | \
+ SPEAR1310_PCIE##x##_CFG_POWERUP_RESET | \
+ SPEAR1310_PCIE##x##_CFG_DEVICE_PRESENT)
+ #define SPEAR1310_SATA_CFG_VAL(x) \
+ (SPEAR1310_PCIE_SATA##x##_SEL_SATA | \
+ SPEAR1310_SATA##x##_CFG_PM_CLK_EN | \
+ SPEAR1310_SATA##x##_CFG_POWERUP_RESET | \
+ SPEAR1310_SATA##x##_CFG_RX_CLK_EN | \
+ SPEAR1310_SATA##x##_CFG_TX_CLK_EN)
+
+#define SPEAR1310_PCIE_MIPHY_CFG_1 0x3A8
+ #define SPEAR1310_MIPHY_DUAL_OSC_BYPASS_EXT BIT(31)
+ #define SPEAR1310_MIPHY_DUAL_CLK_REF_DIV2 BIT(28)
+ #define SPEAR1310_MIPHY_DUAL_PLL_RATIO_TOP(x) (x << 16)
+ #define SPEAR1310_MIPHY_SINGLE_OSC_BYPASS_EXT BIT(15)
+ #define SPEAR1310_MIPHY_SINGLE_CLK_REF_DIV2 BIT(12)
+ #define SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(x) (x << 0)
+ #define SPEAR1310_PCIE_SATA_MIPHY_CFG_SATA_MASK (0xFFFF)
+ #define SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE_MASK (0xFFFF << 16)
+ #define SPEAR1310_PCIE_SATA_MIPHY_CFG_SATA \
+ (SPEAR1310_MIPHY_DUAL_OSC_BYPASS_EXT | \
+ SPEAR1310_MIPHY_DUAL_CLK_REF_DIV2 | \
+ SPEAR1310_MIPHY_DUAL_PLL_RATIO_TOP(60) | \
+ SPEAR1310_MIPHY_SINGLE_OSC_BYPASS_EXT | \
+ SPEAR1310_MIPHY_SINGLE_CLK_REF_DIV2 | \
+ SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(60))
+ #define SPEAR1310_PCIE_SATA_MIPHY_CFG_SATA_25M_CRYSTAL_CLK \
+ (SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(120))
+ #define SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE \
+ (SPEAR1310_MIPHY_DUAL_OSC_BYPASS_EXT | \
+ SPEAR1310_MIPHY_DUAL_PLL_RATIO_TOP(25) | \
+ SPEAR1310_MIPHY_SINGLE_OSC_BYPASS_EXT | \
+ SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(25))
+
+#define SPEAR1310_PCIE_MIPHY_CFG_2 0x3AC
+
+enum spear1310_miphy_mode {
+ SATA,
+ PCIE,
+};
+
+struct spear1310_miphy_priv {
+ /* instance id of this phy */
+ u32 id;
+ /* phy mode: 0 for SATA 1 for PCIe */
+ enum spear1310_miphy_mode mode;
+ /* regmap for any soc specific misc registers */
+ struct regmap *misc;
+ /* phy struct pointer */
+ struct phy *phy;
+};
+
+static int spear1310_miphy_pcie_init(struct spear1310_miphy_priv *priv)
+{
+ u32 val;
+
+ regmap_update_bits(priv->misc, SPEAR1310_PCIE_MIPHY_CFG_1,
+ SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE_MASK,
+ SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE);
+
+ switch (priv->id) {
+ case 0:
+ val = SPEAR1310_PCIE_CFG_VAL(0);
+ break;
+ case 1:
+ val = SPEAR1310_PCIE_CFG_VAL(1);
+ break;
+ case 2:
+ val = SPEAR1310_PCIE_CFG_VAL(2);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_update_bits(priv->misc, SPEAR1310_PCIE_SATA_CFG,
+ SPEAR1310_PCIE_CFG_MASK(priv->id), val);
+
+ return 0;
+}
+
+static int spear1310_miphy_pcie_exit(struct spear1310_miphy_priv *priv)
+{
+ regmap_update_bits(priv->misc, SPEAR1310_PCIE_SATA_CFG,
+ SPEAR1310_PCIE_CFG_MASK(priv->id), 0);
+
+ regmap_update_bits(priv->misc, SPEAR1310_PCIE_MIPHY_CFG_1,
+ SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE_MASK, 0);
+
+ return 0;
+}
+
+static int spear1310_miphy_init(struct phy *phy)
+{
+ struct spear1310_miphy_priv *priv = phy_get_drvdata(phy);
+ int ret = 0;
+
+ if (priv->mode == PCIE)
+ ret = spear1310_miphy_pcie_init(priv);
+
+ return ret;
+}
+
+static int spear1310_miphy_exit(struct phy *phy)
+{
+ struct spear1310_miphy_priv *priv = phy_get_drvdata(phy);
+ int ret = 0;
+
+ if (priv->mode == PCIE)
+ ret = spear1310_miphy_pcie_exit(priv);
+
+ return ret;
+}
+
+static const struct of_device_id spear1310_miphy_of_match[] = {
+ { .compatible = "st,spear1310-miphy" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, spear1310_miphy_of_match);
+
+static const struct phy_ops spear1310_miphy_ops = {
+ .init = spear1310_miphy_init,
+ .exit = spear1310_miphy_exit,
+ .owner = THIS_MODULE,
+};
+
+static struct phy *spear1310_miphy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct spear1310_miphy_priv *priv = dev_get_drvdata(dev);
+
+ if (args->args_count < 1) {
+ dev_err(dev, "DT did not pass correct no of args\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ priv->mode = args->args[0];
+
+ if (priv->mode != SATA && priv->mode != PCIE) {
+ dev_err(dev, "DT did not pass correct phy mode\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ return priv->phy;
+}
+
+static int spear1310_miphy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct spear1310_miphy_priv *priv;
+ struct phy_provider *phy_provider;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->misc =
+ syscon_regmap_lookup_by_phandle(dev->of_node, "misc");
+ if (IS_ERR(priv->misc)) {
+ dev_err(dev, "failed to find misc regmap\n");
+ return PTR_ERR(priv->misc);
+ }
+
+ if (of_property_read_u32(dev->of_node, "phy-id", &priv->id)) {
+ dev_err(dev, "failed to find phy id\n");
+ return -EINVAL;
+ }
+
+ priv->phy = devm_phy_create(dev, NULL, &spear1310_miphy_ops);
+ if (IS_ERR(priv->phy)) {
+ dev_err(dev, "failed to create SATA PCIe PHY\n");
+ return PTR_ERR(priv->phy);
+ }
+
+ dev_set_drvdata(dev, priv);
+ phy_set_drvdata(priv->phy, priv);
+
+ phy_provider =
+ devm_of_phy_provider_register(dev, spear1310_miphy_xlate);
+ if (IS_ERR(phy_provider)) {
+ dev_err(dev, "failed to register phy provider\n");
+ return PTR_ERR(phy_provider);
+ }
+
+ return 0;
+}
+
+static struct platform_driver spear1310_miphy_driver = {
+ .probe = spear1310_miphy_probe,
+ .driver = {
+ .name = "spear1310-miphy",
+ .of_match_table = spear1310_miphy_of_match,
+ },
+};
+
+module_platform_driver(spear1310_miphy_driver);
+
+MODULE_DESCRIPTION("ST SPEAR1310-MIPHY driver");
+MODULE_AUTHOR("Pratyush Anand <pratyush.anand@gmail.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/st/phy-spear1340-miphy.c b/drivers/phy/st/phy-spear1340-miphy.c
new file mode 100644
index 0000000000..34a1cf2101
--- /dev/null
+++ b/drivers/phy/st/phy-spear1340-miphy.c
@@ -0,0 +1,291 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ST spear1340-miphy driver
+ *
+ * Copyright (C) 2014 ST Microelectronics
+ * Pratyush Anand <pratyush.anand@gmail.com>
+ * Mohit Kumar <mohit.kumar.dhaka@gmail.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/kernel.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/regmap.h>
+
+/* SPEAr1340 Registers */
+/* Power Management Registers */
+#define SPEAR1340_PCM_CFG 0x100
+ #define SPEAR1340_PCM_CFG_SATA_POWER_EN BIT(11)
+#define SPEAR1340_PCM_WKUP_CFG 0x104
+#define SPEAR1340_SWITCH_CTR 0x108
+
+#define SPEAR1340_PERIP1_SW_RST 0x318
+ #define SPEAR1340_PERIP1_SW_RSATA BIT(12)
+#define SPEAR1340_PERIP2_SW_RST 0x31C
+#define SPEAR1340_PERIP3_SW_RST 0x320
+
+/* PCIE - SATA configuration registers */
+#define SPEAR1340_PCIE_SATA_CFG 0x424
+ /* PCIE CFG MASks */
+ #define SPEAR1340_PCIE_CFG_DEVICE_PRESENT BIT(11)
+ #define SPEAR1340_PCIE_CFG_POWERUP_RESET BIT(10)
+ #define SPEAR1340_PCIE_CFG_CORE_CLK_EN BIT(9)
+ #define SPEAR1340_PCIE_CFG_AUX_CLK_EN BIT(8)
+ #define SPEAR1340_SATA_CFG_TX_CLK_EN BIT(4)
+ #define SPEAR1340_SATA_CFG_RX_CLK_EN BIT(3)
+ #define SPEAR1340_SATA_CFG_POWERUP_RESET BIT(2)
+ #define SPEAR1340_SATA_CFG_PM_CLK_EN BIT(1)
+ #define SPEAR1340_PCIE_SATA_SEL_PCIE (0)
+ #define SPEAR1340_PCIE_SATA_SEL_SATA (1)
+ #define SPEAR1340_PCIE_SATA_CFG_MASK 0xF1F
+ #define SPEAR1340_PCIE_CFG_VAL (SPEAR1340_PCIE_SATA_SEL_PCIE | \
+ SPEAR1340_PCIE_CFG_AUX_CLK_EN | \
+ SPEAR1340_PCIE_CFG_CORE_CLK_EN | \
+ SPEAR1340_PCIE_CFG_POWERUP_RESET | \
+ SPEAR1340_PCIE_CFG_DEVICE_PRESENT)
+ #define SPEAR1340_SATA_CFG_VAL (SPEAR1340_PCIE_SATA_SEL_SATA | \
+ SPEAR1340_SATA_CFG_PM_CLK_EN | \
+ SPEAR1340_SATA_CFG_POWERUP_RESET | \
+ SPEAR1340_SATA_CFG_RX_CLK_EN | \
+ SPEAR1340_SATA_CFG_TX_CLK_EN)
+
+#define SPEAR1340_PCIE_MIPHY_CFG 0x428
+ #define SPEAR1340_MIPHY_OSC_BYPASS_EXT BIT(31)
+ #define SPEAR1340_MIPHY_CLK_REF_DIV2 BIT(27)
+ #define SPEAR1340_MIPHY_CLK_REF_DIV4 (2 << 27)
+ #define SPEAR1340_MIPHY_CLK_REF_DIV8 (3 << 27)
+ #define SPEAR1340_MIPHY_PLL_RATIO_TOP(x) (x << 0)
+ #define SPEAR1340_PCIE_MIPHY_CFG_MASK 0xF80000FF
+ #define SPEAR1340_PCIE_SATA_MIPHY_CFG_SATA \
+ (SPEAR1340_MIPHY_OSC_BYPASS_EXT | \
+ SPEAR1340_MIPHY_CLK_REF_DIV2 | \
+ SPEAR1340_MIPHY_PLL_RATIO_TOP(60))
+ #define SPEAR1340_PCIE_SATA_MIPHY_CFG_SATA_25M_CRYSTAL_CLK \
+ (SPEAR1340_MIPHY_PLL_RATIO_TOP(120))
+ #define SPEAR1340_PCIE_SATA_MIPHY_CFG_PCIE \
+ (SPEAR1340_MIPHY_OSC_BYPASS_EXT | \
+ SPEAR1340_MIPHY_PLL_RATIO_TOP(25))
+
+enum spear1340_miphy_mode {
+ SATA,
+ PCIE,
+};
+
+struct spear1340_miphy_priv {
+ /* phy mode: 0 for SATA 1 for PCIe */
+ enum spear1340_miphy_mode mode;
+ /* regmap for any soc specific misc registers */
+ struct regmap *misc;
+ /* phy struct pointer */
+ struct phy *phy;
+};
+
+static int spear1340_miphy_sata_init(struct spear1340_miphy_priv *priv)
+{
+ regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG,
+ SPEAR1340_PCIE_SATA_CFG_MASK,
+ SPEAR1340_SATA_CFG_VAL);
+ regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG,
+ SPEAR1340_PCIE_MIPHY_CFG_MASK,
+ SPEAR1340_PCIE_SATA_MIPHY_CFG_SATA_25M_CRYSTAL_CLK);
+ /* Switch on sata power domain */
+ regmap_update_bits(priv->misc, SPEAR1340_PCM_CFG,
+ SPEAR1340_PCM_CFG_SATA_POWER_EN,
+ SPEAR1340_PCM_CFG_SATA_POWER_EN);
+ /* Wait for SATA power domain on */
+ msleep(20);
+
+ /* Disable PCIE SATA Controller reset */
+ regmap_update_bits(priv->misc, SPEAR1340_PERIP1_SW_RST,
+ SPEAR1340_PERIP1_SW_RSATA, 0);
+ /* Wait for SATA reset de-assert completion */
+ msleep(20);
+
+ return 0;
+}
+
+static int spear1340_miphy_sata_exit(struct spear1340_miphy_priv *priv)
+{
+ regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG,
+ SPEAR1340_PCIE_SATA_CFG_MASK, 0);
+ regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG,
+ SPEAR1340_PCIE_MIPHY_CFG_MASK, 0);
+
+ /* Enable PCIE SATA Controller reset */
+ regmap_update_bits(priv->misc, SPEAR1340_PERIP1_SW_RST,
+ SPEAR1340_PERIP1_SW_RSATA,
+ SPEAR1340_PERIP1_SW_RSATA);
+ /* Wait for SATA power domain off */
+ msleep(20);
+ /* Switch off sata power domain */
+ regmap_update_bits(priv->misc, SPEAR1340_PCM_CFG,
+ SPEAR1340_PCM_CFG_SATA_POWER_EN, 0);
+ /* Wait for SATA reset assert completion */
+ msleep(20);
+
+ return 0;
+}
+
+static int spear1340_miphy_pcie_init(struct spear1340_miphy_priv *priv)
+{
+ regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG,
+ SPEAR1340_PCIE_MIPHY_CFG_MASK,
+ SPEAR1340_PCIE_SATA_MIPHY_CFG_PCIE);
+ regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG,
+ SPEAR1340_PCIE_SATA_CFG_MASK,
+ SPEAR1340_PCIE_CFG_VAL);
+
+ return 0;
+}
+
+static int spear1340_miphy_pcie_exit(struct spear1340_miphy_priv *priv)
+{
+ regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG,
+ SPEAR1340_PCIE_MIPHY_CFG_MASK, 0);
+ regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG,
+ SPEAR1340_PCIE_SATA_CFG_MASK, 0);
+
+ return 0;
+}
+
+static int spear1340_miphy_init(struct phy *phy)
+{
+ struct spear1340_miphy_priv *priv = phy_get_drvdata(phy);
+ int ret = 0;
+
+ if (priv->mode == SATA)
+ ret = spear1340_miphy_sata_init(priv);
+ else if (priv->mode == PCIE)
+ ret = spear1340_miphy_pcie_init(priv);
+
+ return ret;
+}
+
+static int spear1340_miphy_exit(struct phy *phy)
+{
+ struct spear1340_miphy_priv *priv = phy_get_drvdata(phy);
+ int ret = 0;
+
+ if (priv->mode == SATA)
+ ret = spear1340_miphy_sata_exit(priv);
+ else if (priv->mode == PCIE)
+ ret = spear1340_miphy_pcie_exit(priv);
+
+ return ret;
+}
+
+static const struct of_device_id spear1340_miphy_of_match[] = {
+ { .compatible = "st,spear1340-miphy" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, spear1340_miphy_of_match);
+
+static const struct phy_ops spear1340_miphy_ops = {
+ .init = spear1340_miphy_init,
+ .exit = spear1340_miphy_exit,
+ .owner = THIS_MODULE,
+};
+
+#ifdef CONFIG_PM_SLEEP
+static int spear1340_miphy_suspend(struct device *dev)
+{
+ struct spear1340_miphy_priv *priv = dev_get_drvdata(dev);
+ int ret = 0;
+
+ if (priv->mode == SATA)
+ ret = spear1340_miphy_sata_exit(priv);
+
+ return ret;
+}
+
+static int spear1340_miphy_resume(struct device *dev)
+{
+ struct spear1340_miphy_priv *priv = dev_get_drvdata(dev);
+ int ret = 0;
+
+ if (priv->mode == SATA)
+ ret = spear1340_miphy_sata_init(priv);
+
+ return ret;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(spear1340_miphy_pm_ops, spear1340_miphy_suspend,
+ spear1340_miphy_resume);
+
+static struct phy *spear1340_miphy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct spear1340_miphy_priv *priv = dev_get_drvdata(dev);
+
+ if (args->args_count < 1) {
+ dev_err(dev, "DT did not pass correct no of args\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ priv->mode = args->args[0];
+
+ if (priv->mode != SATA && priv->mode != PCIE) {
+ dev_err(dev, "DT did not pass correct phy mode\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ return priv->phy;
+}
+
+static int spear1340_miphy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct spear1340_miphy_priv *priv;
+ struct phy_provider *phy_provider;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->misc =
+ syscon_regmap_lookup_by_phandle(dev->of_node, "misc");
+ if (IS_ERR(priv->misc)) {
+ dev_err(dev, "failed to find misc regmap\n");
+ return PTR_ERR(priv->misc);
+ }
+
+ priv->phy = devm_phy_create(dev, NULL, &spear1340_miphy_ops);
+ if (IS_ERR(priv->phy)) {
+ dev_err(dev, "failed to create SATA PCIe PHY\n");
+ return PTR_ERR(priv->phy);
+ }
+
+ dev_set_drvdata(dev, priv);
+ phy_set_drvdata(priv->phy, priv);
+
+ phy_provider =
+ devm_of_phy_provider_register(dev, spear1340_miphy_xlate);
+ if (IS_ERR(phy_provider)) {
+ dev_err(dev, "failed to register phy provider\n");
+ return PTR_ERR(phy_provider);
+ }
+
+ return 0;
+}
+
+static struct platform_driver spear1340_miphy_driver = {
+ .probe = spear1340_miphy_probe,
+ .driver = {
+ .name = "spear1340-miphy",
+ .pm = &spear1340_miphy_pm_ops,
+ .of_match_table = spear1340_miphy_of_match,
+ },
+};
+
+module_platform_driver(spear1340_miphy_driver);
+
+MODULE_DESCRIPTION("ST SPEAR1340-MIPHY driver");
+MODULE_AUTHOR("Pratyush Anand <pratyush.anand@gmail.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/st/phy-stih407-usb.c b/drivers/phy/st/phy-stih407-usb.c
new file mode 100644
index 0000000000..a4ae2cca7f
--- /dev/null
+++ b/drivers/phy/st/phy-stih407-usb.c
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2014 STMicroelectronics
+ *
+ * STMicroelectronics Generic PHY driver for STiH407 USB2.
+ *
+ * Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
+ */
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/clk.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/mfd/syscon.h>
+#include <linux/phy/phy.h>
+
+#define PHYPARAM_REG 1
+#define PHYCTRL_REG 2
+
+/* Default PHY_SEL and REFCLKSEL configuration */
+#define STIH407_USB_PICOPHY_CTRL_PORT_CONF 0x6
+#define STIH407_USB_PICOPHY_CTRL_PORT_MASK 0x1f
+
+/* ports parameters overriding */
+#define STIH407_USB_PICOPHY_PARAM_DEF 0x39a4dc
+#define STIH407_USB_PICOPHY_PARAM_MASK 0xffffffff
+
+struct stih407_usb2_picophy {
+ struct phy *phy;
+ struct regmap *regmap;
+ struct device *dev;
+ struct reset_control *rstc;
+ struct reset_control *rstport;
+ int ctrl;
+ int param;
+};
+
+static int stih407_usb2_pico_ctrl(struct stih407_usb2_picophy *phy_dev)
+{
+ reset_control_deassert(phy_dev->rstc);
+
+ return regmap_update_bits(phy_dev->regmap, phy_dev->ctrl,
+ STIH407_USB_PICOPHY_CTRL_PORT_MASK,
+ STIH407_USB_PICOPHY_CTRL_PORT_CONF);
+}
+
+static int stih407_usb2_init_port(struct phy *phy)
+{
+ int ret;
+ struct stih407_usb2_picophy *phy_dev = phy_get_drvdata(phy);
+
+ stih407_usb2_pico_ctrl(phy_dev);
+
+ ret = regmap_update_bits(phy_dev->regmap,
+ phy_dev->param,
+ STIH407_USB_PICOPHY_PARAM_MASK,
+ STIH407_USB_PICOPHY_PARAM_DEF);
+ if (ret)
+ return ret;
+
+ return reset_control_deassert(phy_dev->rstport);
+}
+
+static int stih407_usb2_exit_port(struct phy *phy)
+{
+ struct stih407_usb2_picophy *phy_dev = phy_get_drvdata(phy);
+
+ /*
+ * Only port reset is asserted, phy global reset is kept untouched
+ * as other ports may still be active. When all ports are in reset
+ * state, assumption is made that power will be cut off on the phy, in
+ * case of suspend for instance. Theoretically, asserting individual
+ * reset (like here) or global reset should be equivalent.
+ */
+ return reset_control_assert(phy_dev->rstport);
+}
+
+static const struct phy_ops stih407_usb2_picophy_data = {
+ .init = stih407_usb2_init_port,
+ .exit = stih407_usb2_exit_port,
+ .owner = THIS_MODULE,
+};
+
+static int stih407_usb2_picophy_probe(struct platform_device *pdev)
+{
+ struct stih407_usb2_picophy *phy_dev;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct phy_provider *phy_provider;
+ struct phy *phy;
+ int ret;
+
+ phy_dev = devm_kzalloc(dev, sizeof(*phy_dev), GFP_KERNEL);
+ if (!phy_dev)
+ return -ENOMEM;
+
+ phy_dev->dev = dev;
+ dev_set_drvdata(dev, phy_dev);
+
+ phy_dev->rstc = devm_reset_control_get_shared(dev, "global");
+ if (IS_ERR(phy_dev->rstc)) {
+ dev_err(dev, "failed to ctrl picoPHY reset\n");
+ return PTR_ERR(phy_dev->rstc);
+ }
+
+ phy_dev->rstport = devm_reset_control_get_exclusive(dev, "port");
+ if (IS_ERR(phy_dev->rstport)) {
+ dev_err(dev, "failed to ctrl picoPHY reset\n");
+ return PTR_ERR(phy_dev->rstport);
+ }
+
+ /* Reset port by default: only deassert it in phy init */
+ reset_control_assert(phy_dev->rstport);
+
+ phy_dev->regmap = syscon_regmap_lookup_by_phandle(np, "st,syscfg");
+ if (IS_ERR(phy_dev->regmap)) {
+ dev_err(dev, "No syscfg phandle specified\n");
+ return PTR_ERR(phy_dev->regmap);
+ }
+
+ ret = of_property_read_u32_index(np, "st,syscfg", PHYPARAM_REG,
+ &phy_dev->param);
+ if (ret) {
+ dev_err(dev, "can't get phyparam offset (%d)\n", ret);
+ return ret;
+ }
+
+ ret = of_property_read_u32_index(np, "st,syscfg", PHYCTRL_REG,
+ &phy_dev->ctrl);
+ if (ret) {
+ dev_err(dev, "can't get phyctrl offset (%d)\n", ret);
+ return ret;
+ }
+
+ phy = devm_phy_create(dev, NULL, &stih407_usb2_picophy_data);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "failed to create Display Port PHY\n");
+ return PTR_ERR(phy);
+ }
+
+ phy_dev->phy = phy;
+ phy_set_drvdata(phy, phy_dev);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(phy_provider))
+ return PTR_ERR(phy_provider);
+
+ dev_info(dev, "STiH407 USB Generic picoPHY driver probed!");
+
+ return 0;
+}
+
+static const struct of_device_id stih407_usb2_picophy_of_match[] = {
+ { .compatible = "st,stih407-usb2-phy" },
+ { /*sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, stih407_usb2_picophy_of_match);
+
+static struct platform_driver stih407_usb2_picophy_driver = {
+ .probe = stih407_usb2_picophy_probe,
+ .driver = {
+ .name = "stih407-usb-genphy",
+ .of_match_table = stih407_usb2_picophy_of_match,
+ }
+};
+
+module_platform_driver(stih407_usb2_picophy_driver);
+
+MODULE_AUTHOR("Giuseppe Cavallaro <peppe.cavallaro@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics Generic picoPHY driver for STiH407");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/st/phy-stm32-usbphyc.c b/drivers/phy/st/phy-stm32-usbphyc.c
new file mode 100644
index 0000000000..d5e7e44000
--- /dev/null
+++ b/drivers/phy/st/phy-stm32-usbphyc.c
@@ -0,0 +1,826 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * STMicroelectronics STM32 USB PHY Controller driver
+ *
+ * Copyright (C) 2018 STMicroelectronics
+ * Author(s): Amelie Delaunay <amelie.delaunay@st.com>.
+ */
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/units.h>
+
+#define STM32_USBPHYC_PLL 0x0
+#define STM32_USBPHYC_MISC 0x8
+#define STM32_USBPHYC_MONITOR(X) (0x108 + ((X) * 0x100))
+#define STM32_USBPHYC_TUNE(X) (0x10C + ((X) * 0x100))
+#define STM32_USBPHYC_VERSION 0x3F4
+
+/* STM32_USBPHYC_PLL bit fields */
+#define PLLNDIV GENMASK(6, 0)
+#define PLLFRACIN GENMASK(25, 10)
+#define PLLEN BIT(26)
+#define PLLSTRB BIT(27)
+#define PLLSTRBYP BIT(28)
+#define PLLFRACCTL BIT(29)
+#define PLLDITHEN0 BIT(30)
+#define PLLDITHEN1 BIT(31)
+
+/* STM32_USBPHYC_MISC bit fields */
+#define SWITHOST BIT(0)
+
+/* STM32_USBPHYC_MONITOR bit fields */
+#define STM32_USBPHYC_MON_OUT GENMASK(3, 0)
+#define STM32_USBPHYC_MON_SEL GENMASK(8, 4)
+#define STM32_USBPHYC_MON_SEL_LOCKP 0x1F
+#define STM32_USBPHYC_MON_OUT_LOCKP BIT(3)
+
+/* STM32_USBPHYC_TUNE bit fields */
+#define INCURREN BIT(0)
+#define INCURRINT BIT(1)
+#define LFSCAPEN BIT(2)
+#define HSDRVSLEW BIT(3)
+#define HSDRVDCCUR BIT(4)
+#define HSDRVDCLEV BIT(5)
+#define HSDRVCURINCR BIT(6)
+#define FSDRVRFADJ BIT(7)
+#define HSDRVRFRED BIT(8)
+#define HSDRVCHKITRM GENMASK(12, 9)
+#define HSDRVCHKZTRM GENMASK(14, 13)
+#define OTPCOMP GENMASK(19, 15)
+#define SQLCHCTL GENMASK(21, 20)
+#define HDRXGNEQEN BIT(22)
+#define HSRXOFF GENMASK(24, 23)
+#define HSFALLPREEM BIT(25)
+#define SHTCCTCTLPROT BIT(26)
+#define STAGSEL BIT(27)
+
+enum boosting_vals {
+ BOOST_1000_UA = 1000,
+ BOOST_2000_UA = 2000,
+};
+
+enum dc_level_vals {
+ DC_NOMINAL,
+ DC_PLUS_5_TO_7_MV,
+ DC_PLUS_10_TO_14_MV,
+ DC_MINUS_5_TO_7_MV,
+ DC_MAX,
+};
+
+enum current_trim {
+ CUR_NOMINAL,
+ CUR_PLUS_1_56_PCT,
+ CUR_PLUS_3_12_PCT,
+ CUR_PLUS_4_68_PCT,
+ CUR_PLUS_6_24_PCT,
+ CUR_PLUS_7_8_PCT,
+ CUR_PLUS_9_36_PCT,
+ CUR_PLUS_10_92_PCT,
+ CUR_PLUS_12_48_PCT,
+ CUR_PLUS_14_04_PCT,
+ CUR_PLUS_15_6_PCT,
+ CUR_PLUS_17_16_PCT,
+ CUR_PLUS_19_01_PCT,
+ CUR_PLUS_20_58_PCT,
+ CUR_PLUS_22_16_PCT,
+ CUR_PLUS_23_73_PCT,
+ CUR_MAX,
+};
+
+enum impedance_trim {
+ IMP_NOMINAL,
+ IMP_MINUS_2_OHMS,
+ IMP_MINUS_4_OMHS,
+ IMP_MINUS_6_OHMS,
+ IMP_MAX,
+};
+
+enum squelch_level {
+ SQLCH_NOMINAL,
+ SQLCH_PLUS_7_MV,
+ SQLCH_MINUS_5_MV,
+ SQLCH_PLUS_14_MV,
+ SQLCH_MAX,
+};
+
+enum rx_offset {
+ NO_RX_OFFSET,
+ RX_OFFSET_PLUS_5_MV,
+ RX_OFFSET_PLUS_10_MV,
+ RX_OFFSET_MINUS_5_MV,
+ RX_OFFSET_MAX,
+};
+
+/* STM32_USBPHYC_VERSION bit fields */
+#define MINREV GENMASK(3, 0)
+#define MAJREV GENMASK(7, 4)
+
+#define PLL_FVCO_MHZ 2880
+#define PLL_INFF_MIN_RATE_HZ 19200000
+#define PLL_INFF_MAX_RATE_HZ 38400000
+
+struct pll_params {
+ u8 ndiv;
+ u16 frac;
+};
+
+struct stm32_usbphyc_phy {
+ struct phy *phy;
+ struct stm32_usbphyc *usbphyc;
+ struct regulator *vbus;
+ u32 index;
+ bool active;
+ u32 tune;
+};
+
+struct stm32_usbphyc {
+ struct device *dev;
+ void __iomem *base;
+ struct clk *clk;
+ struct reset_control *rst;
+ struct stm32_usbphyc_phy **phys;
+ int nphys;
+ struct regulator *vdda1v1;
+ struct regulator *vdda1v8;
+ atomic_t n_pll_cons;
+ struct clk_hw clk48_hw;
+ int switch_setup;
+};
+
+static inline void stm32_usbphyc_set_bits(void __iomem *reg, u32 bits)
+{
+ writel_relaxed(readl_relaxed(reg) | bits, reg);
+}
+
+static inline void stm32_usbphyc_clr_bits(void __iomem *reg, u32 bits)
+{
+ writel_relaxed(readl_relaxed(reg) & ~bits, reg);
+}
+
+static int stm32_usbphyc_regulators_enable(struct stm32_usbphyc *usbphyc)
+{
+ int ret;
+
+ ret = regulator_enable(usbphyc->vdda1v1);
+ if (ret)
+ return ret;
+
+ ret = regulator_enable(usbphyc->vdda1v8);
+ if (ret)
+ goto vdda1v1_disable;
+
+ return 0;
+
+vdda1v1_disable:
+ regulator_disable(usbphyc->vdda1v1);
+
+ return ret;
+}
+
+static int stm32_usbphyc_regulators_disable(struct stm32_usbphyc *usbphyc)
+{
+ int ret;
+
+ ret = regulator_disable(usbphyc->vdda1v8);
+ if (ret)
+ return ret;
+
+ ret = regulator_disable(usbphyc->vdda1v1);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void stm32_usbphyc_get_pll_params(u32 clk_rate,
+ struct pll_params *pll_params)
+{
+ unsigned long long fvco, ndiv, frac;
+
+ /* _
+ * | FVCO = INFF*2*(NDIV + FRACT/2^16) when DITHER_DISABLE[1] = 1
+ * | FVCO = 2880MHz
+ * <
+ * | NDIV = integer part of input bits to set the LDF
+ * |_FRACT = fractional part of input bits to set the LDF
+ * => PLLNDIV = integer part of (FVCO / (INFF*2))
+ * => PLLFRACIN = fractional part of(FVCO / INFF*2) * 2^16
+ * <=> PLLFRACIN = ((FVCO / (INFF*2)) - PLLNDIV) * 2^16
+ */
+ fvco = (unsigned long long)PLL_FVCO_MHZ * HZ_PER_MHZ;
+
+ ndiv = fvco;
+ do_div(ndiv, (clk_rate * 2));
+ pll_params->ndiv = (u8)ndiv;
+
+ frac = fvco * (1 << 16);
+ do_div(frac, (clk_rate * 2));
+ frac = frac - (ndiv * (1 << 16));
+ pll_params->frac = (u16)frac;
+}
+
+static int stm32_usbphyc_pll_init(struct stm32_usbphyc *usbphyc)
+{
+ struct pll_params pll_params;
+ u32 clk_rate = clk_get_rate(usbphyc->clk);
+ u32 ndiv, frac;
+ u32 usbphyc_pll;
+
+ if ((clk_rate < PLL_INFF_MIN_RATE_HZ) ||
+ (clk_rate > PLL_INFF_MAX_RATE_HZ)) {
+ dev_err(usbphyc->dev, "input clk freq (%dHz) out of range\n",
+ clk_rate);
+ return -EINVAL;
+ }
+
+ stm32_usbphyc_get_pll_params(clk_rate, &pll_params);
+ ndiv = FIELD_PREP(PLLNDIV, pll_params.ndiv);
+ frac = FIELD_PREP(PLLFRACIN, pll_params.frac);
+
+ usbphyc_pll = PLLDITHEN1 | PLLDITHEN0 | PLLSTRBYP | ndiv;
+
+ if (pll_params.frac)
+ usbphyc_pll |= PLLFRACCTL | frac;
+
+ writel_relaxed(usbphyc_pll, usbphyc->base + STM32_USBPHYC_PLL);
+
+ dev_dbg(usbphyc->dev, "input clk freq=%dHz, ndiv=%lu, frac=%lu\n",
+ clk_rate, FIELD_GET(PLLNDIV, usbphyc_pll),
+ FIELD_GET(PLLFRACIN, usbphyc_pll));
+
+ return 0;
+}
+
+static int __stm32_usbphyc_pll_disable(struct stm32_usbphyc *usbphyc)
+{
+ void __iomem *pll_reg = usbphyc->base + STM32_USBPHYC_PLL;
+ u32 pllen;
+
+ stm32_usbphyc_clr_bits(pll_reg, PLLEN);
+
+ /* Wait for minimum width of powerdown pulse (ENABLE = Low) */
+ if (readl_relaxed_poll_timeout(pll_reg, pllen, !(pllen & PLLEN), 5, 50))
+ dev_err(usbphyc->dev, "PLL not reset\n");
+
+ return stm32_usbphyc_regulators_disable(usbphyc);
+}
+
+static int stm32_usbphyc_pll_disable(struct stm32_usbphyc *usbphyc)
+{
+ /* Check if a phy port is still active or clk48 in use */
+ if (atomic_dec_return(&usbphyc->n_pll_cons) > 0)
+ return 0;
+
+ return __stm32_usbphyc_pll_disable(usbphyc);
+}
+
+static int stm32_usbphyc_pll_enable(struct stm32_usbphyc *usbphyc)
+{
+ void __iomem *pll_reg = usbphyc->base + STM32_USBPHYC_PLL;
+ bool pllen = readl_relaxed(pll_reg) & PLLEN;
+ int ret;
+
+ /*
+ * Check if a phy port or clk48 prepare has configured the pll
+ * and ensure the PLL is enabled
+ */
+ if (atomic_inc_return(&usbphyc->n_pll_cons) > 1 && pllen)
+ return 0;
+
+ if (pllen) {
+ /*
+ * PLL shouldn't be enabled without known consumer,
+ * disable it and reinit n_pll_cons
+ */
+ dev_warn(usbphyc->dev, "PLL enabled without known consumers\n");
+
+ ret = __stm32_usbphyc_pll_disable(usbphyc);
+ if (ret)
+ goto dec_n_pll_cons;
+ }
+
+ ret = stm32_usbphyc_regulators_enable(usbphyc);
+ if (ret)
+ goto dec_n_pll_cons;
+
+ ret = stm32_usbphyc_pll_init(usbphyc);
+ if (ret)
+ goto reg_disable;
+
+ stm32_usbphyc_set_bits(pll_reg, PLLEN);
+
+ /* Wait for maximum lock time */
+ usleep_range(200, 300);
+
+ return 0;
+
+reg_disable:
+ stm32_usbphyc_regulators_disable(usbphyc);
+
+dec_n_pll_cons:
+ atomic_dec(&usbphyc->n_pll_cons);
+
+ return ret;
+}
+
+static int stm32_usbphyc_phy_init(struct phy *phy)
+{
+ struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy);
+ struct stm32_usbphyc *usbphyc = usbphyc_phy->usbphyc;
+ u32 reg_mon = STM32_USBPHYC_MONITOR(usbphyc_phy->index);
+ u32 monsel = FIELD_PREP(STM32_USBPHYC_MON_SEL,
+ STM32_USBPHYC_MON_SEL_LOCKP);
+ u32 monout;
+ int ret;
+
+ ret = stm32_usbphyc_pll_enable(usbphyc);
+ if (ret)
+ return ret;
+
+ /* Check that PLL Lock input to PHY is High */
+ writel_relaxed(monsel, usbphyc->base + reg_mon);
+ ret = readl_relaxed_poll_timeout(usbphyc->base + reg_mon, monout,
+ (monout & STM32_USBPHYC_MON_OUT_LOCKP),
+ 100, 1000);
+ if (ret) {
+ dev_err(usbphyc->dev, "PLL Lock input to PHY is Low (val=%x)\n",
+ (u32)(monout & STM32_USBPHYC_MON_OUT));
+ goto pll_disable;
+ }
+
+ usbphyc_phy->active = true;
+
+ return 0;
+
+pll_disable:
+ stm32_usbphyc_pll_disable(usbphyc);
+
+ return ret;
+}
+
+static int stm32_usbphyc_phy_exit(struct phy *phy)
+{
+ struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy);
+ struct stm32_usbphyc *usbphyc = usbphyc_phy->usbphyc;
+
+ usbphyc_phy->active = false;
+
+ return stm32_usbphyc_pll_disable(usbphyc);
+}
+
+static int stm32_usbphyc_phy_power_on(struct phy *phy)
+{
+ struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy);
+
+ if (usbphyc_phy->vbus)
+ return regulator_enable(usbphyc_phy->vbus);
+
+ return 0;
+}
+
+static int stm32_usbphyc_phy_power_off(struct phy *phy)
+{
+ struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy);
+
+ if (usbphyc_phy->vbus)
+ return regulator_disable(usbphyc_phy->vbus);
+
+ return 0;
+}
+
+static const struct phy_ops stm32_usbphyc_phy_ops = {
+ .init = stm32_usbphyc_phy_init,
+ .exit = stm32_usbphyc_phy_exit,
+ .power_on = stm32_usbphyc_phy_power_on,
+ .power_off = stm32_usbphyc_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static int stm32_usbphyc_clk48_prepare(struct clk_hw *hw)
+{
+ struct stm32_usbphyc *usbphyc = container_of(hw, struct stm32_usbphyc, clk48_hw);
+
+ return stm32_usbphyc_pll_enable(usbphyc);
+}
+
+static void stm32_usbphyc_clk48_unprepare(struct clk_hw *hw)
+{
+ struct stm32_usbphyc *usbphyc = container_of(hw, struct stm32_usbphyc, clk48_hw);
+
+ stm32_usbphyc_pll_disable(usbphyc);
+}
+
+static unsigned long stm32_usbphyc_clk48_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
+{
+ return 48000000;
+}
+
+static const struct clk_ops usbphyc_clk48_ops = {
+ .prepare = stm32_usbphyc_clk48_prepare,
+ .unprepare = stm32_usbphyc_clk48_unprepare,
+ .recalc_rate = stm32_usbphyc_clk48_recalc_rate,
+};
+
+static void stm32_usbphyc_clk48_unregister(void *data)
+{
+ struct stm32_usbphyc *usbphyc = data;
+
+ of_clk_del_provider(usbphyc->dev->of_node);
+ clk_hw_unregister(&usbphyc->clk48_hw);
+}
+
+static int stm32_usbphyc_clk48_register(struct stm32_usbphyc *usbphyc)
+{
+ struct device_node *node = usbphyc->dev->of_node;
+ struct clk_init_data init = { };
+ int ret = 0;
+
+ init.name = "ck_usbo_48m";
+ init.ops = &usbphyc_clk48_ops;
+
+ usbphyc->clk48_hw.init = &init;
+
+ ret = clk_hw_register(usbphyc->dev, &usbphyc->clk48_hw);
+ if (ret)
+ return ret;
+
+ ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, &usbphyc->clk48_hw);
+ if (ret)
+ clk_hw_unregister(&usbphyc->clk48_hw);
+
+ return ret;
+}
+
+static void stm32_usbphyc_phy_tuning(struct stm32_usbphyc *usbphyc,
+ struct device_node *np, u32 index)
+{
+ struct stm32_usbphyc_phy *usbphyc_phy = usbphyc->phys[index];
+ u32 reg = STM32_USBPHYC_TUNE(index);
+ u32 otpcomp, val;
+ int ret;
+
+ /* Backup OTP compensation code */
+ otpcomp = FIELD_GET(OTPCOMP, readl_relaxed(usbphyc->base + reg));
+
+ ret = of_property_read_u32(np, "st,current-boost-microamp", &val);
+ if (ret != -EINVAL) {
+ if (!ret && (val == BOOST_1000_UA || val == BOOST_2000_UA)) {
+ val = (val == BOOST_2000_UA) ? 1 : 0;
+ usbphyc_phy->tune |= INCURREN | FIELD_PREP(INCURRINT, val);
+ } else {
+ dev_warn(usbphyc->dev, "phy%d: invalid st,current-boost-microamp\n", index);
+ }
+ }
+
+ if (!of_property_read_bool(np, "st,no-lsfs-fb-cap"))
+ usbphyc_phy->tune |= LFSCAPEN;
+
+ if (of_property_read_bool(np, "st,decrease-hs-slew-rate"))
+ usbphyc_phy->tune |= HSDRVSLEW;
+
+ ret = of_property_read_u32(np, "st,tune-hs-dc-level", &val);
+ if (ret != -EINVAL) {
+ if (!ret && val < DC_MAX) {
+ if (val == DC_MINUS_5_TO_7_MV) {/* Decreases HS driver DC level */
+ usbphyc_phy->tune |= HSDRVDCCUR;
+ } else if (val > 0) { /* Increases HS driver DC level */
+ val = (val == DC_PLUS_10_TO_14_MV) ? 1 : 0;
+ usbphyc_phy->tune |= HSDRVCURINCR | FIELD_PREP(HSDRVDCLEV, val);
+ }
+ } else {
+ dev_warn(usbphyc->dev, "phy%d: invalid st,tune-hs-dc-level\n", index);
+ }
+ }
+
+ if (of_property_read_bool(np, "st,enable-fs-rftime-tuning"))
+ usbphyc_phy->tune |= FSDRVRFADJ;
+
+ if (of_property_read_bool(np, "st,enable-hs-rftime-reduction"))
+ usbphyc_phy->tune |= HSDRVRFRED;
+
+ ret = of_property_read_u32(np, "st,trim-hs-current", &val);
+ if (ret != -EINVAL) {
+ if (!ret && val < CUR_MAX)
+ usbphyc_phy->tune |= FIELD_PREP(HSDRVCHKITRM, val);
+ else
+ dev_warn(usbphyc->dev, "phy%d: invalid st,trim-hs-current\n", index);
+ }
+
+ ret = of_property_read_u32(np, "st,trim-hs-impedance", &val);
+ if (ret != -EINVAL) {
+ if (!ret && val < IMP_MAX)
+ usbphyc_phy->tune |= FIELD_PREP(HSDRVCHKZTRM, val);
+ else
+ dev_warn(usbphyc->dev, "phy%d: invalid st,trim-hs-impedance\n", index);
+ }
+
+ ret = of_property_read_u32(np, "st,tune-squelch-level", &val);
+ if (ret != -EINVAL) {
+ if (!ret && val < SQLCH_MAX)
+ usbphyc_phy->tune |= FIELD_PREP(SQLCHCTL, val);
+ else
+ dev_warn(usbphyc->dev, "phy%d: invalid st,tune-squelch\n", index);
+ }
+
+ if (of_property_read_bool(np, "st,enable-hs-rx-gain-eq"))
+ usbphyc_phy->tune |= HDRXGNEQEN;
+
+ ret = of_property_read_u32(np, "st,tune-hs-rx-offset", &val);
+ if (ret != -EINVAL) {
+ if (!ret && val < RX_OFFSET_MAX)
+ usbphyc_phy->tune |= FIELD_PREP(HSRXOFF, val);
+ else
+ dev_warn(usbphyc->dev, "phy%d: invalid st,tune-hs-rx-offset\n", index);
+ }
+
+ if (of_property_read_bool(np, "st,no-hs-ftime-ctrl"))
+ usbphyc_phy->tune |= HSFALLPREEM;
+
+ if (!of_property_read_bool(np, "st,no-lsfs-sc"))
+ usbphyc_phy->tune |= SHTCCTCTLPROT;
+
+ if (of_property_read_bool(np, "st,enable-hs-tx-staggering"))
+ usbphyc_phy->tune |= STAGSEL;
+
+ /* Restore OTP compensation code */
+ usbphyc_phy->tune |= FIELD_PREP(OTPCOMP, otpcomp);
+
+ /*
+ * By default, if no st,xxx tuning property is used, usbphyc_phy->tune is equal to
+ * STM32_USBPHYC_TUNE reset value (LFSCAPEN | SHTCCTCTLPROT | OTPCOMP).
+ */
+ writel_relaxed(usbphyc_phy->tune, usbphyc->base + reg);
+}
+
+static void stm32_usbphyc_switch_setup(struct stm32_usbphyc *usbphyc,
+ u32 utmi_switch)
+{
+ if (!utmi_switch)
+ stm32_usbphyc_clr_bits(usbphyc->base + STM32_USBPHYC_MISC,
+ SWITHOST);
+ else
+ stm32_usbphyc_set_bits(usbphyc->base + STM32_USBPHYC_MISC,
+ SWITHOST);
+ usbphyc->switch_setup = utmi_switch;
+}
+
+static struct phy *stm32_usbphyc_of_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct stm32_usbphyc *usbphyc = dev_get_drvdata(dev);
+ struct stm32_usbphyc_phy *usbphyc_phy = NULL;
+ struct device_node *phynode = args->np;
+ int port = 0;
+
+ for (port = 0; port < usbphyc->nphys; port++) {
+ if (phynode == usbphyc->phys[port]->phy->dev.of_node) {
+ usbphyc_phy = usbphyc->phys[port];
+ break;
+ }
+ }
+ if (!usbphyc_phy) {
+ dev_err(dev, "failed to find phy\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (((usbphyc_phy->index == 0) && (args->args_count != 0)) ||
+ ((usbphyc_phy->index == 1) && (args->args_count != 1))) {
+ dev_err(dev, "invalid number of cells for phy port%d\n",
+ usbphyc_phy->index);
+ return ERR_PTR(-EINVAL);
+ }
+
+ /* Configure the UTMI switch for PHY port#2 */
+ if (usbphyc_phy->index == 1) {
+ if (usbphyc->switch_setup < 0) {
+ stm32_usbphyc_switch_setup(usbphyc, args->args[0]);
+ } else {
+ if (args->args[0] != usbphyc->switch_setup) {
+ dev_err(dev, "phy port1 already used\n");
+ return ERR_PTR(-EBUSY);
+ }
+ }
+ }
+
+ return usbphyc_phy->phy;
+}
+
+static int stm32_usbphyc_probe(struct platform_device *pdev)
+{
+ struct stm32_usbphyc *usbphyc;
+ struct device *dev = &pdev->dev;
+ struct device_node *child, *np = dev->of_node;
+ struct phy_provider *phy_provider;
+ u32 pllen, version;
+ int ret, port = 0;
+
+ usbphyc = devm_kzalloc(dev, sizeof(*usbphyc), GFP_KERNEL);
+ if (!usbphyc)
+ return -ENOMEM;
+ usbphyc->dev = dev;
+ dev_set_drvdata(dev, usbphyc);
+
+ usbphyc->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(usbphyc->base))
+ return PTR_ERR(usbphyc->base);
+
+ usbphyc->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(usbphyc->clk))
+ return dev_err_probe(dev, PTR_ERR(usbphyc->clk), "clk get_failed\n");
+
+ ret = clk_prepare_enable(usbphyc->clk);
+ if (ret) {
+ dev_err(dev, "clk enable failed: %d\n", ret);
+ return ret;
+ }
+
+ usbphyc->rst = devm_reset_control_get(dev, NULL);
+ if (!IS_ERR(usbphyc->rst)) {
+ reset_control_assert(usbphyc->rst);
+ udelay(2);
+ reset_control_deassert(usbphyc->rst);
+ } else {
+ ret = PTR_ERR(usbphyc->rst);
+ if (ret == -EPROBE_DEFER)
+ goto clk_disable;
+
+ stm32_usbphyc_clr_bits(usbphyc->base + STM32_USBPHYC_PLL, PLLEN);
+ }
+
+ /*
+ * Wait for minimum width of powerdown pulse (ENABLE = Low):
+ * we have to ensure the PLL is disabled before phys initialization.
+ */
+ if (readl_relaxed_poll_timeout(usbphyc->base + STM32_USBPHYC_PLL,
+ pllen, !(pllen & PLLEN), 5, 50)) {
+ dev_warn(usbphyc->dev, "PLL not reset\n");
+ ret = -EPROBE_DEFER;
+ goto clk_disable;
+ }
+
+ usbphyc->switch_setup = -EINVAL;
+ usbphyc->nphys = of_get_child_count(np);
+ usbphyc->phys = devm_kcalloc(dev, usbphyc->nphys,
+ sizeof(*usbphyc->phys), GFP_KERNEL);
+ if (!usbphyc->phys) {
+ ret = -ENOMEM;
+ goto clk_disable;
+ }
+
+ usbphyc->vdda1v1 = devm_regulator_get(dev, "vdda1v1");
+ if (IS_ERR(usbphyc->vdda1v1)) {
+ ret = dev_err_probe(dev, PTR_ERR(usbphyc->vdda1v1),
+ "failed to get vdda1v1 supply\n");
+ goto clk_disable;
+ }
+
+ usbphyc->vdda1v8 = devm_regulator_get(dev, "vdda1v8");
+ if (IS_ERR(usbphyc->vdda1v8)) {
+ ret = dev_err_probe(dev, PTR_ERR(usbphyc->vdda1v8),
+ "failed to get vdda1v8 supply\n");
+ goto clk_disable;
+ }
+
+ for_each_child_of_node(np, child) {
+ struct stm32_usbphyc_phy *usbphyc_phy;
+ struct phy *phy;
+ u32 index;
+
+ phy = devm_phy_create(dev, child, &stm32_usbphyc_phy_ops);
+ if (IS_ERR(phy)) {
+ ret = PTR_ERR(phy);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to create phy%d: %d\n",
+ port, ret);
+ goto put_child;
+ }
+
+ usbphyc_phy = devm_kzalloc(dev, sizeof(*usbphyc_phy),
+ GFP_KERNEL);
+ if (!usbphyc_phy) {
+ ret = -ENOMEM;
+ goto put_child;
+ }
+
+ ret = of_property_read_u32(child, "reg", &index);
+ if (ret || index > usbphyc->nphys) {
+ dev_err(&phy->dev, "invalid reg property: %d\n", ret);
+ if (!ret)
+ ret = -EINVAL;
+ goto put_child;
+ }
+
+ usbphyc->phys[port] = usbphyc_phy;
+ phy_set_bus_width(phy, 8);
+ phy_set_drvdata(phy, usbphyc_phy);
+
+ usbphyc->phys[port]->phy = phy;
+ usbphyc->phys[port]->usbphyc = usbphyc;
+ usbphyc->phys[port]->index = index;
+ usbphyc->phys[port]->active = false;
+
+ usbphyc->phys[port]->vbus = devm_regulator_get_optional(&phy->dev, "vbus");
+ if (IS_ERR(usbphyc->phys[port]->vbus)) {
+ ret = PTR_ERR(usbphyc->phys[port]->vbus);
+ if (ret == -EPROBE_DEFER)
+ goto put_child;
+ usbphyc->phys[port]->vbus = NULL;
+ }
+
+ /* Configure phy tuning */
+ stm32_usbphyc_phy_tuning(usbphyc, child, index);
+
+ port++;
+ }
+
+ phy_provider = devm_of_phy_provider_register(dev,
+ stm32_usbphyc_of_xlate);
+ if (IS_ERR(phy_provider)) {
+ ret = PTR_ERR(phy_provider);
+ dev_err(dev, "failed to register phy provider: %d\n", ret);
+ goto clk_disable;
+ }
+
+ ret = stm32_usbphyc_clk48_register(usbphyc);
+ if (ret) {
+ dev_err(dev, "failed to register ck_usbo_48m clock: %d\n", ret);
+ goto clk_disable;
+ }
+
+ version = readl_relaxed(usbphyc->base + STM32_USBPHYC_VERSION);
+ dev_info(dev, "registered rev:%lu.%lu\n",
+ FIELD_GET(MAJREV, version), FIELD_GET(MINREV, version));
+
+ return 0;
+
+put_child:
+ of_node_put(child);
+clk_disable:
+ clk_disable_unprepare(usbphyc->clk);
+
+ return ret;
+}
+
+static void stm32_usbphyc_remove(struct platform_device *pdev)
+{
+ struct stm32_usbphyc *usbphyc = dev_get_drvdata(&pdev->dev);
+ int port;
+
+ /* Ensure PHYs are not active, to allow PLL disabling */
+ for (port = 0; port < usbphyc->nphys; port++)
+ if (usbphyc->phys[port]->active)
+ stm32_usbphyc_phy_exit(usbphyc->phys[port]->phy);
+
+ stm32_usbphyc_clk48_unregister(usbphyc);
+
+ clk_disable_unprepare(usbphyc->clk);
+}
+
+static int __maybe_unused stm32_usbphyc_resume(struct device *dev)
+{
+ struct stm32_usbphyc *usbphyc = dev_get_drvdata(dev);
+ struct stm32_usbphyc_phy *usbphyc_phy;
+ int port;
+
+ if (usbphyc->switch_setup >= 0)
+ stm32_usbphyc_switch_setup(usbphyc, usbphyc->switch_setup);
+
+ for (port = 0; port < usbphyc->nphys; port++) {
+ usbphyc_phy = usbphyc->phys[port];
+ writel_relaxed(usbphyc_phy->tune, usbphyc->base + STM32_USBPHYC_TUNE(port));
+ }
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(stm32_usbphyc_pm_ops, NULL, stm32_usbphyc_resume);
+
+static const struct of_device_id stm32_usbphyc_of_match[] = {
+ { .compatible = "st,stm32mp1-usbphyc", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, stm32_usbphyc_of_match);
+
+static struct platform_driver stm32_usbphyc_driver = {
+ .probe = stm32_usbphyc_probe,
+ .remove_new = stm32_usbphyc_remove,
+ .driver = {
+ .of_match_table = stm32_usbphyc_of_match,
+ .name = "stm32-usbphyc",
+ .pm = &stm32_usbphyc_pm_ops,
+ }
+};
+module_platform_driver(stm32_usbphyc_driver);
+
+MODULE_DESCRIPTION("STMicroelectronics STM32 USBPHYC driver");
+MODULE_AUTHOR("Amelie Delaunay <amelie.delaunay@st.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/starfive/Kconfig b/drivers/phy/starfive/Kconfig
new file mode 100644
index 0000000000..9508e21430
--- /dev/null
+++ b/drivers/phy/starfive/Kconfig
@@ -0,0 +1,38 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Phy drivers for StarFive platforms
+#
+
+if ARCH_STARFIVE || COMPILE_TEST
+
+config PHY_STARFIVE_JH7110_DPHY_RX
+ tristate "StarFive JH7110 D-PHY RX support"
+ depends on HAS_IOMEM
+ select GENERIC_PHY
+ select GENERIC_PHY_MIPI_DPHY
+ help
+ Choose this option if you have a StarFive D-PHY in your
+ system. If M is selected, the module will be called
+ phy-jh7110-dphy-rx.ko.
+
+config PHY_STARFIVE_JH7110_PCIE
+ tristate "Starfive JH7110 PCIE 2.0/USB 3.0 PHY support"
+ depends on HAS_IOMEM
+ select GENERIC_PHY
+ help
+ Enable this to support the StarFive PCIe 2.0 PHY,
+ or used as USB 3.0 PHY.
+ If M is selected, the module will be called
+ phy-jh7110-pcie.ko.
+
+config PHY_STARFIVE_JH7110_USB
+ tristate "Starfive JH7110 USB 2.0 PHY support"
+ depends on USB_SUPPORT
+ select GENERIC_PHY
+ help
+ Enable this to support the StarFive USB 2.0 PHY,
+ used with the Cadence USB controller.
+ If M is selected, the module will be called
+ phy-jh7110-usb.ko.
+
+endif # ARCH_STARFIVE || COMPILE_TEST
diff --git a/drivers/phy/starfive/Makefile b/drivers/phy/starfive/Makefile
new file mode 100644
index 0000000000..b391018b7c
--- /dev/null
+++ b/drivers/phy/starfive/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_PHY_STARFIVE_JH7110_DPHY_RX) += phy-jh7110-dphy-rx.o
+obj-$(CONFIG_PHY_STARFIVE_JH7110_PCIE) += phy-jh7110-pcie.o
+obj-$(CONFIG_PHY_STARFIVE_JH7110_USB) += phy-jh7110-usb.o
diff --git a/drivers/phy/starfive/phy-jh7110-dphy-rx.c b/drivers/phy/starfive/phy-jh7110-dphy-rx.c
new file mode 100644
index 0000000000..037a9e0263
--- /dev/null
+++ b/drivers/phy/starfive/phy-jh7110-dphy-rx.c
@@ -0,0 +1,232 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * StarFive JH7110 DPHY RX driver
+ *
+ * Copyright (C) 2023 StarFive Technology Co., Ltd.
+ * Author: Jack Zhu <jack.zhu@starfivetech.com>
+ * Author: Changhuang Liang <changhuang.liang@starfivetech.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+
+#define STF_DPHY_APBCFGSAIF_SYSCFG(x) (x)
+
+#define STF_DPHY_ENABLE_CLK BIT(6)
+#define STF_DPHY_ENABLE_CLK1 BIT(7)
+#define STF_DPHY_ENABLE_LAN0 BIT(8)
+#define STF_DPHY_ENABLE_LAN1 BIT(9)
+#define STF_DPHY_ENABLE_LAN2 BIT(10)
+#define STF_DPHY_ENABLE_LAN3 BIT(11)
+#define STF_DPHY_LANE_SWAP_CLK GENMASK(22, 20)
+#define STF_DPHY_LANE_SWAP_CLK1 GENMASK(25, 23)
+#define STF_DPHY_LANE_SWAP_LAN0 GENMASK(28, 26)
+#define STF_DPHY_LANE_SWAP_LAN1 GENMASK(31, 29)
+
+#define STF_DPHY_LANE_SWAP_LAN2 GENMASK(2, 0)
+#define STF_DPHY_LANE_SWAP_LAN3 GENMASK(5, 3)
+#define STF_DPHY_PLL_CLK_SEL GENMASK(21, 12)
+#define STF_DPHY_PRECOUNTER_IN_CLK GENMASK(29, 22)
+
+#define STF_DPHY_PRECOUNTER_IN_CLK1 GENMASK(7, 0)
+#define STF_DPHY_PRECOUNTER_IN_LAN0 GENMASK(15, 8)
+#define STF_DPHY_PRECOUNTER_IN_LAN1 GENMASK(23, 16)
+#define STF_DPHY_PRECOUNTER_IN_LAN2 GENMASK(31, 24)
+
+#define STF_DPHY_PRECOUNTER_IN_LAN3 GENMASK(7, 0)
+#define STF_DPHY_RX_1C2C_SEL BIT(8)
+
+#define STF_MAP_LANES_NUM 6
+
+struct regval {
+ u32 addr;
+ u32 val;
+};
+
+struct stf_dphy_info {
+ /**
+ * @maps:
+ *
+ * Physical lanes and logic lanes mapping table.
+ *
+ * The default order is:
+ * [clk lane0, data lane 0, data lane 1, data lane 2, date lane 3, clk lane 1]
+ */
+ u8 maps[STF_MAP_LANES_NUM];
+};
+
+struct stf_dphy {
+ struct device *dev;
+ void __iomem *regs;
+ struct clk *cfg_clk;
+ struct clk *ref_clk;
+ struct clk *tx_clk;
+ struct reset_control *rstc;
+ struct regulator *mipi_0p9;
+ struct phy *phy;
+ const struct stf_dphy_info *info;
+};
+
+static int stf_dphy_configure(struct phy *phy, union phy_configure_opts *opts)
+{
+ struct stf_dphy *dphy = phy_get_drvdata(phy);
+ const struct stf_dphy_info *info = dphy->info;
+
+ writel(FIELD_PREP(STF_DPHY_ENABLE_CLK, 1) |
+ FIELD_PREP(STF_DPHY_ENABLE_CLK1, 1) |
+ FIELD_PREP(STF_DPHY_ENABLE_LAN0, 1) |
+ FIELD_PREP(STF_DPHY_ENABLE_LAN1, 1) |
+ FIELD_PREP(STF_DPHY_ENABLE_LAN2, 1) |
+ FIELD_PREP(STF_DPHY_ENABLE_LAN3, 1) |
+ FIELD_PREP(STF_DPHY_LANE_SWAP_CLK, info->maps[0]) |
+ FIELD_PREP(STF_DPHY_LANE_SWAP_CLK1, info->maps[5]) |
+ FIELD_PREP(STF_DPHY_LANE_SWAP_LAN0, info->maps[1]) |
+ FIELD_PREP(STF_DPHY_LANE_SWAP_LAN1, info->maps[2]),
+ dphy->regs + STF_DPHY_APBCFGSAIF_SYSCFG(188));
+
+ writel(FIELD_PREP(STF_DPHY_LANE_SWAP_LAN2, info->maps[3]) |
+ FIELD_PREP(STF_DPHY_LANE_SWAP_LAN3, info->maps[4]) |
+ FIELD_PREP(STF_DPHY_PRECOUNTER_IN_CLK, 8),
+ dphy->regs + STF_DPHY_APBCFGSAIF_SYSCFG(192));
+
+ writel(FIELD_PREP(STF_DPHY_PRECOUNTER_IN_CLK1, 8) |
+ FIELD_PREP(STF_DPHY_PRECOUNTER_IN_LAN0, 7) |
+ FIELD_PREP(STF_DPHY_PRECOUNTER_IN_LAN1, 7) |
+ FIELD_PREP(STF_DPHY_PRECOUNTER_IN_LAN2, 7),
+ dphy->regs + STF_DPHY_APBCFGSAIF_SYSCFG(196));
+
+ writel(FIELD_PREP(STF_DPHY_PRECOUNTER_IN_LAN3, 7),
+ dphy->regs + STF_DPHY_APBCFGSAIF_SYSCFG(200));
+
+ return 0;
+}
+
+static int stf_dphy_power_on(struct phy *phy)
+{
+ struct stf_dphy *dphy = phy_get_drvdata(phy);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(dphy->dev);
+ if (ret < 0)
+ return ret;
+
+ ret = regulator_enable(dphy->mipi_0p9);
+ if (ret) {
+ pm_runtime_put(dphy->dev);
+ return ret;
+ }
+
+ clk_set_rate(dphy->cfg_clk, 99000000);
+ clk_set_rate(dphy->ref_clk, 49500000);
+ clk_set_rate(dphy->tx_clk, 19800000);
+ reset_control_deassert(dphy->rstc);
+
+ return 0;
+}
+
+static int stf_dphy_power_off(struct phy *phy)
+{
+ struct stf_dphy *dphy = phy_get_drvdata(phy);
+
+ reset_control_assert(dphy->rstc);
+
+ regulator_disable(dphy->mipi_0p9);
+
+ pm_runtime_put_sync(dphy->dev);
+
+ return 0;
+}
+
+static const struct phy_ops stf_dphy_ops = {
+ .configure = stf_dphy_configure,
+ .power_on = stf_dphy_power_on,
+ .power_off = stf_dphy_power_off,
+};
+
+static int stf_dphy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct stf_dphy *dphy;
+
+ dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL);
+ if (!dphy)
+ return -ENOMEM;
+
+ dphy->info = of_device_get_match_data(&pdev->dev);
+
+ dev_set_drvdata(&pdev->dev, dphy);
+ dphy->dev = &pdev->dev;
+
+ dphy->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(dphy->regs))
+ return PTR_ERR(dphy->regs);
+
+ dphy->cfg_clk = devm_clk_get(&pdev->dev, "cfg");
+ if (IS_ERR(dphy->cfg_clk))
+ return PTR_ERR(dphy->cfg_clk);
+
+ dphy->ref_clk = devm_clk_get(&pdev->dev, "ref");
+ if (IS_ERR(dphy->ref_clk))
+ return PTR_ERR(dphy->ref_clk);
+
+ dphy->tx_clk = devm_clk_get(&pdev->dev, "tx");
+ if (IS_ERR(dphy->tx_clk))
+ return PTR_ERR(dphy->tx_clk);
+
+ dphy->rstc = devm_reset_control_array_get_exclusive(&pdev->dev);
+ if (IS_ERR(dphy->rstc))
+ return PTR_ERR(dphy->rstc);
+
+ dphy->mipi_0p9 = devm_regulator_get(&pdev->dev, "mipi_0p9");
+ if (IS_ERR(dphy->mipi_0p9))
+ return PTR_ERR(dphy->mipi_0p9);
+
+ dphy->phy = devm_phy_create(&pdev->dev, NULL, &stf_dphy_ops);
+ if (IS_ERR(dphy->phy)) {
+ dev_err(&pdev->dev, "Failed to create PHY\n");
+ return PTR_ERR(dphy->phy);
+ }
+
+ pm_runtime_enable(&pdev->dev);
+
+ phy_set_drvdata(dphy->phy, dphy);
+ phy_provider = devm_of_phy_provider_register(&pdev->dev,
+ of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct stf_dphy_info starfive_dphy_info = {
+ .maps = {4, 0, 1, 2, 3, 5},
+};
+
+static const struct of_device_id stf_dphy_dt_ids[] = {
+ {
+ .compatible = "starfive,jh7110-dphy-rx",
+ .data = &starfive_dphy_info,
+ },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, stf_dphy_dt_ids);
+
+static struct platform_driver stf_dphy_driver = {
+ .probe = stf_dphy_probe,
+ .driver = {
+ .name = "starfive-dphy-rx",
+ .of_match_table = stf_dphy_dt_ids,
+ },
+};
+module_platform_driver(stf_dphy_driver);
+
+MODULE_AUTHOR("Jack Zhu <jack.zhu@starfivetech.com>");
+MODULE_AUTHOR("Changhuang Liang <changhuang.liang@starfivetech.com>");
+MODULE_DESCRIPTION("StarFive JH7110 DPHY RX driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/starfive/phy-jh7110-pcie.c b/drivers/phy/starfive/phy-jh7110-pcie.c
new file mode 100644
index 0000000000..734c8e0077
--- /dev/null
+++ b/drivers/phy/starfive/phy-jh7110-pcie.c
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * StarFive JH7110 PCIe 2.0 PHY driver
+ *
+ * Copyright (C) 2023 StarFive Technology Co., Ltd.
+ * Author: Minda Chen <minda.chen@starfivetech.com>
+ */
+
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mfd/syscon.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define PCIE_KVCO_LEVEL_OFF 0x28
+#define PCIE_USB3_PHY_PLL_CTL_OFF 0x7c
+#define PCIE_KVCO_TUNE_SIGNAL_OFF 0x80
+#define PCIE_USB3_PHY_ENABLE BIT(4)
+#define PHY_KVCO_FINE_TUNE_LEVEL 0x91
+#define PHY_KVCO_FINE_TUNE_SIGNALS 0xc
+
+#define USB_PDRSTN_SPLIT BIT(17)
+
+#define PCIE_PHY_MODE BIT(20)
+#define PCIE_PHY_MODE_MASK GENMASK(21, 20)
+#define PCIE_USB3_BUS_WIDTH_MASK GENMASK(3, 2)
+#define PCIE_USB3_BUS_WIDTH BIT(3)
+#define PCIE_USB3_RATE_MASK GENMASK(6, 5)
+#define PCIE_USB3_RX_STANDBY_MASK BIT(7)
+#define PCIE_USB3_PHY_ENABLE BIT(4)
+
+struct jh7110_pcie_phy {
+ struct phy *phy;
+ struct regmap *stg_syscon;
+ struct regmap *sys_syscon;
+ void __iomem *regs;
+ u32 sys_phy_connect;
+ u32 stg_pcie_mode;
+ u32 stg_pcie_usb;
+ enum phy_mode mode;
+};
+
+static int phy_usb3_mode_set(struct jh7110_pcie_phy *data)
+{
+ if (!data->stg_syscon || !data->sys_syscon) {
+ dev_err(&data->phy->dev, "doesn't support usb3 mode\n");
+ return -EINVAL;
+ }
+
+ regmap_update_bits(data->stg_syscon, data->stg_pcie_mode,
+ PCIE_PHY_MODE_MASK, PCIE_PHY_MODE);
+ regmap_update_bits(data->stg_syscon, data->stg_pcie_usb,
+ PCIE_USB3_BUS_WIDTH_MASK, 0);
+ regmap_update_bits(data->stg_syscon, data->stg_pcie_usb,
+ PCIE_USB3_PHY_ENABLE, PCIE_USB3_PHY_ENABLE);
+
+ /* Connect usb 3.0 phy mode */
+ regmap_update_bits(data->sys_syscon, data->sys_phy_connect,
+ USB_PDRSTN_SPLIT, 0);
+
+ /* Configuare spread-spectrum mode: down-spread-spectrum */
+ writel(PCIE_USB3_PHY_ENABLE, data->regs + PCIE_USB3_PHY_PLL_CTL_OFF);
+
+ return 0;
+}
+
+static void phy_pcie_mode_set(struct jh7110_pcie_phy *data)
+{
+ u32 val;
+
+ /* default is PCIe mode */
+ if (!data->stg_syscon || !data->sys_syscon)
+ return;
+
+ regmap_update_bits(data->stg_syscon, data->stg_pcie_mode,
+ PCIE_PHY_MODE_MASK, 0);
+ regmap_update_bits(data->stg_syscon, data->stg_pcie_usb,
+ PCIE_USB3_BUS_WIDTH_MASK,
+ PCIE_USB3_BUS_WIDTH);
+ regmap_update_bits(data->stg_syscon, data->stg_pcie_usb,
+ PCIE_USB3_PHY_ENABLE, 0);
+
+ regmap_update_bits(data->sys_syscon, data->sys_phy_connect,
+ USB_PDRSTN_SPLIT, 0);
+
+ val = readl(data->regs + PCIE_USB3_PHY_PLL_CTL_OFF);
+ val &= ~PCIE_USB3_PHY_ENABLE;
+ writel(val, data->regs + PCIE_USB3_PHY_PLL_CTL_OFF);
+}
+
+static void phy_kvco_gain_set(struct jh7110_pcie_phy *phy)
+{
+ /* PCIe Multi-PHY PLL KVCO Gain fine tune settings: */
+ writel(PHY_KVCO_FINE_TUNE_LEVEL, phy->regs + PCIE_KVCO_LEVEL_OFF);
+ writel(PHY_KVCO_FINE_TUNE_SIGNALS, phy->regs + PCIE_KVCO_TUNE_SIGNAL_OFF);
+}
+
+static int jh7110_pcie_phy_set_mode(struct phy *_phy,
+ enum phy_mode mode, int submode)
+{
+ struct jh7110_pcie_phy *phy = phy_get_drvdata(_phy);
+ int ret;
+
+ if (mode == phy->mode)
+ return 0;
+
+ switch (mode) {
+ case PHY_MODE_USB_HOST:
+ case PHY_MODE_USB_DEVICE:
+ case PHY_MODE_USB_OTG:
+ ret = phy_usb3_mode_set(phy);
+ if (ret)
+ return ret;
+ break;
+ case PHY_MODE_PCIE:
+ phy_pcie_mode_set(phy);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(&_phy->dev, "Changing phy mode to %d\n", mode);
+ phy->mode = mode;
+
+ return 0;
+}
+
+static const struct phy_ops jh7110_pcie_phy_ops = {
+ .set_mode = jh7110_pcie_phy_set_mode,
+ .owner = THIS_MODULE,
+};
+
+static int jh7110_pcie_phy_probe(struct platform_device *pdev)
+{
+ struct jh7110_pcie_phy *phy;
+ struct device *dev = &pdev->dev;
+ struct phy_provider *phy_provider;
+ u32 args[2];
+
+ phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+ if (!phy)
+ return -ENOMEM;
+
+ phy->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(phy->regs))
+ return PTR_ERR(phy->regs);
+
+ phy->phy = devm_phy_create(dev, NULL, &jh7110_pcie_phy_ops);
+ if (IS_ERR(phy->phy))
+ return dev_err_probe(dev, PTR_ERR(phy->phy),
+ "Failed to map phy base\n");
+
+ phy->sys_syscon =
+ syscon_regmap_lookup_by_phandle_args(pdev->dev.of_node,
+ "starfive,sys-syscon",
+ 1, args);
+
+ if (!IS_ERR_OR_NULL(phy->sys_syscon))
+ phy->sys_phy_connect = args[0];
+ else
+ phy->sys_syscon = NULL;
+
+ phy->stg_syscon =
+ syscon_regmap_lookup_by_phandle_args(pdev->dev.of_node,
+ "starfive,stg-syscon",
+ 2, args);
+
+ if (!IS_ERR_OR_NULL(phy->stg_syscon)) {
+ phy->stg_pcie_mode = args[0];
+ phy->stg_pcie_usb = args[1];
+ } else {
+ phy->stg_syscon = NULL;
+ }
+
+ phy_kvco_gain_set(phy);
+
+ phy_set_drvdata(phy->phy, phy);
+ 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 jh7110_pcie_phy_of_match[] = {
+ { .compatible = "starfive,jh7110-pcie-phy" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, jh7110_pcie_phy_of_match);
+
+static struct platform_driver jh7110_pcie_phy_driver = {
+ .probe = jh7110_pcie_phy_probe,
+ .driver = {
+ .of_match_table = jh7110_pcie_phy_of_match,
+ .name = "jh7110-pcie-phy",
+ }
+};
+module_platform_driver(jh7110_pcie_phy_driver);
+
+MODULE_DESCRIPTION("StarFive JH7110 PCIe 2.0 PHY driver");
+MODULE_AUTHOR("Minda Chen <minda.chen@starfivetech.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/starfive/phy-jh7110-usb.c b/drivers/phy/starfive/phy-jh7110-usb.c
new file mode 100644
index 0000000000..633912f8a0
--- /dev/null
+++ b/drivers/phy/starfive/phy-jh7110-usb.c
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * StarFive JH7110 USB 2.0 PHY driver
+ *
+ * Copyright (C) 2023 StarFive Technology Co., Ltd.
+ * Author: Minda Chen <minda.chen@starfivetech.com>
+ */
+
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/usb/of.h>
+
+#define USB_125M_CLK_RATE 125000000
+#define USB_LS_KEEPALIVE_OFF 0x4
+#define USB_LS_KEEPALIVE_ENABLE BIT(4)
+
+struct jh7110_usb2_phy {
+ struct phy *phy;
+ void __iomem *regs;
+ struct clk *usb_125m_clk;
+ struct clk *app_125m;
+ enum phy_mode mode;
+};
+
+static void usb2_set_ls_keepalive(struct jh7110_usb2_phy *phy, bool set)
+{
+ unsigned int val;
+
+ /* Host mode enable the LS speed keep-alive signal */
+ val = readl(phy->regs + USB_LS_KEEPALIVE_OFF);
+ if (set)
+ val |= USB_LS_KEEPALIVE_ENABLE;
+ else
+ val &= ~USB_LS_KEEPALIVE_ENABLE;
+
+ writel(val, phy->regs + USB_LS_KEEPALIVE_OFF);
+}
+
+static int usb2_phy_set_mode(struct phy *_phy,
+ enum phy_mode mode, int submode)
+{
+ struct jh7110_usb2_phy *phy = phy_get_drvdata(_phy);
+
+ switch (mode) {
+ case PHY_MODE_USB_HOST:
+ case PHY_MODE_USB_DEVICE:
+ case PHY_MODE_USB_OTG:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (mode != phy->mode) {
+ dev_dbg(&_phy->dev, "Changing phy to %d\n", mode);
+ phy->mode = mode;
+ usb2_set_ls_keepalive(phy, (mode != PHY_MODE_USB_DEVICE));
+ }
+
+ return 0;
+}
+
+static int jh7110_usb2_phy_init(struct phy *_phy)
+{
+ struct jh7110_usb2_phy *phy = phy_get_drvdata(_phy);
+ int ret;
+
+ ret = clk_set_rate(phy->usb_125m_clk, USB_125M_CLK_RATE);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(phy->app_125m);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int jh7110_usb2_phy_exit(struct phy *_phy)
+{
+ struct jh7110_usb2_phy *phy = phy_get_drvdata(_phy);
+
+ clk_disable_unprepare(phy->app_125m);
+
+ return 0;
+}
+
+static const struct phy_ops jh7110_usb2_phy_ops = {
+ .init = jh7110_usb2_phy_init,
+ .exit = jh7110_usb2_phy_exit,
+ .set_mode = usb2_phy_set_mode,
+ .owner = THIS_MODULE,
+};
+
+static int jh7110_usb_phy_probe(struct platform_device *pdev)
+{
+ struct jh7110_usb2_phy *phy;
+ struct device *dev = &pdev->dev;
+ struct phy_provider *phy_provider;
+
+ phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+ if (!phy)
+ return -ENOMEM;
+
+ phy->usb_125m_clk = devm_clk_get(dev, "125m");
+ if (IS_ERR(phy->usb_125m_clk))
+ return dev_err_probe(dev, PTR_ERR(phy->usb_125m_clk),
+ "Failed to get 125m clock\n");
+
+ phy->app_125m = devm_clk_get(dev, "app_125m");
+ if (IS_ERR(phy->app_125m))
+ return dev_err_probe(dev, PTR_ERR(phy->app_125m),
+ "Failed to get app 125m clock\n");
+
+ phy->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(phy->regs))
+ return dev_err_probe(dev, PTR_ERR(phy->regs),
+ "Failed to map phy base\n");
+
+ phy->phy = devm_phy_create(dev, NULL, &jh7110_usb2_phy_ops);
+ if (IS_ERR(phy->phy))
+ return dev_err_probe(dev, PTR_ERR(phy->phy),
+ "Failed to create phy\n");
+
+ phy_set_drvdata(phy->phy, phy);
+ 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 jh7110_usb_phy_of_match[] = {
+ { .compatible = "starfive,jh7110-usb-phy" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, jh7110_usb_phy_of_match);
+
+static struct platform_driver jh7110_usb_phy_driver = {
+ .probe = jh7110_usb_phy_probe,
+ .driver = {
+ .of_match_table = jh7110_usb_phy_of_match,
+ .name = "jh7110-usb-phy",
+ }
+};
+module_platform_driver(jh7110_usb_phy_driver);
+
+MODULE_DESCRIPTION("StarFive JH7110 USB 2.0 PHY driver");
+MODULE_AUTHOR("Minda Chen <minda.chen@starfivetech.com>");
+MODULE_LICENSE("GPL");