mirror of
git://git.openwrt.org/openwrt/openwrt.git
synced 2025-12-15 17:12:10 -05:00
Add pending patch for USB support on AN7581 SoC. This is also required to make operational the 3rd PCIe line that use the USB2 Serdes for PCIe operations. Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
668 lines
23 KiB
Diff
668 lines
23 KiB
Diff
From fadd22890b239e5a251dbe47367cfbeb1ea105f7 Mon Sep 17 00:00:00 2001
|
|
From: Christian Marangi <ansuelsmth@gmail.com>
|
|
Date: Fri, 7 Feb 2025 13:28:40 +0100
|
|
Subject: [PATCH 07/10] phy: airoha: Add support for Airoha AN7581 USB PHY
|
|
|
|
Add support for Airoha AN7581 USB PHY driver. AN7581 supports up to 2
|
|
USB port with USB 2.0 mode always supported and USB 3.0 mode available
|
|
only if the Serdes port is correctly configured for USB 3.0.
|
|
|
|
The second USB port on the SoC can be both used for USB 3.0 operation or
|
|
PCIe. (toggled by the SCU SSR register and configured by the USB PHY
|
|
driver)
|
|
|
|
If the USB 3.0 mode is not configured, the modes needs to be also
|
|
disabled in the xHCI node or the driver will report unsable clock and
|
|
fail probe.
|
|
|
|
Also USB 3.0 PHY instance are provided only if the airoha,serdes-port
|
|
and airoha,scu property is defined in DT, if it's not then USB 3.0 PHY
|
|
is assumed not supported.
|
|
|
|
For USB 2.0 Slew Rate calibration, airoha,usb2-monitor-clk-sel is
|
|
mandatory and is used to select the monitor clock for calibration.
|
|
|
|
Normally it's 1 for USB port 1 and 2 for USB port 2.
|
|
|
|
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
|
|
---
|
|
MAINTAINERS | 1 +
|
|
drivers/phy/airoha/Kconfig | 10 +
|
|
drivers/phy/airoha/Makefile | 1 +
|
|
drivers/phy/airoha/phy-airoha-usb.c | 597 ++++++++++++++++++++++++++++
|
|
4 files changed, 609 insertions(+)
|
|
create mode 100644 drivers/phy/airoha/phy-airoha-usb.c
|
|
|
|
--- a/MAINTAINERS
|
|
+++ b/MAINTAINERS
|
|
@@ -742,6 +742,7 @@ M: Christian Marangi <ansuelsmth@gmail.c
|
|
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
|
|
S: Maintained
|
|
F: Documentation/devicetree/bindings/phy/airoha,an7581-usb-phy.yaml
|
|
+F: drivers/phy/airoha/phy-airoha-usb.c
|
|
F: include/dt-bindings/phy/airoha,an7581-usb-phy.h
|
|
|
|
AIRSPY MEDIA DRIVER
|
|
--- a/drivers/phy/airoha/Kconfig
|
|
+++ b/drivers/phy/airoha/Kconfig
|
|
@@ -11,3 +11,13 @@ config PHY_AIROHA_PCIE
|
|
Say Y here to add support for Airoha PCIe PHY driver.
|
|
This driver create the basic PHY instance and provides initialize
|
|
callback for PCIe GEN3 port.
|
|
+
|
|
+config PHY_AIROHA_USB
|
|
+ tristate "Airoha USB PHY Driver"
|
|
+ depends on ARCH_AIROHA || COMPILE_TEST
|
|
+ depends on OF
|
|
+ select GENERIC_PHY
|
|
+ help
|
|
+ Say 'Y' here to add support for Airoha USB PHY driver.
|
|
+ This driver create the basic PHY instance and provides initialize
|
|
+ callback for USB port.
|
|
--- a/drivers/phy/airoha/Makefile
|
|
+++ b/drivers/phy/airoha/Makefile
|
|
@@ -1,3 +1,4 @@
|
|
# SPDX-License-Identifier: GPL-2.0
|
|
|
|
obj-$(CONFIG_PHY_AIROHA_PCIE) += phy-airoha-pcie.o
|
|
+obj-$(CONFIG_PHY_AIROHA_USB) += phy-airoha-usb.o
|
|
--- /dev/null
|
|
+++ b/drivers/phy/airoha/phy-airoha-usb.c
|
|
@@ -0,0 +1,596 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Author: Christian Marangi <ansuelsmth@gmail.com>
|
|
+ */
|
|
+
|
|
+#include <dt-bindings/phy/phy.h>
|
|
+#include <dt-bindings/soc/airoha,scu-ssr.h>
|
|
+#include <linux/bitfield.h>
|
|
+#include <linux/math.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mfd/syscon.h>
|
|
+#include <linux/phy/phy.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/regmap.h>
|
|
+
|
|
+/* SCU */
|
|
+#define AIROHA_SCU_SSTR 0x9c
|
|
+#define AIROHA_SCU_SSTR_USB_PCIE_SEL BIT(3)
|
|
+#define AIROHA_SCU_SSTR_USB_PCIE_SEL_PCIE FIELD_PREP_CONST(AIROHA_SCU_SSTR_USB_PCIE_SEL, 0x0)
|
|
+#define AIROHA_SCU_SSTR_USB_PCIE_SEL_USB FIELD_PREP_CONST(AIROHA_SCU_SSTR_USB_PCIE_SEL, 0x1)
|
|
+
|
|
+/* U2PHY */
|
|
+#define AIROHA_USB_PHY_FMCR0 0x100
|
|
+#define AIROHA_USB_PHY_MONCLK_SEL GENMASK(27, 26)
|
|
+#define AIROHA_USB_PHY_MONCLK_SEL0 FIELD_PREP_CONST(AIROHA_USB_PHY_MONCLK_SEL, 0x0)
|
|
+#define AIROHA_USB_PHY_MONCLK_SEL1 FIELD_PREP_CONST(AIROHA_USB_PHY_MONCLK_SEL, 0x1)
|
|
+#define AIROHA_USB_PHY_MONCLK_SEL2 FIELD_PREP_CONST(AIROHA_USB_PHY_MONCLK_SEL, 0x2)
|
|
+#define AIROHA_USB_PHY_MONCLK_SEL3 FIELD_PREP_CONST(AIROHA_USB_PHY_MONCLK_SEL, 0x3)
|
|
+#define AIROHA_USB_PHY_FREQDET_EN BIT(24)
|
|
+#define AIROHA_USB_PHY_CYCLECNT GENMASK(23, 0)
|
|
+#define AIROHA_USB_PHY_FMMONR0 0x10c
|
|
+#define AIROHA_USB_PHY_USB_FM_OUT GENMASK(31, 0)
|
|
+#define AIROHA_USB_PHY_FMMONR1 0x110
|
|
+#define AIROHA_USB_PHY_FRCK_EN BIT(8)
|
|
+
|
|
+#define AIROHA_USB_PHY_USBPHYACR4 0x310
|
|
+#define AIROHA_USB_PHY_USB20_FS_CR GENMASK(10, 8)
|
|
+#define AIROHA_USB_PHY_USB20_FS_CR_MAX FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_FS_CR, 0x0)
|
|
+#define AIROHA_USB_PHY_USB20_FS_CR_NORMAL FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_FS_CR, 0x2)
|
|
+#define AIROHA_USB_PHY_USB20_FS_CR_SMALLER FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_FS_CR, 0x4)
|
|
+#define AIROHA_USB_PHY_USB20_FS_CR_MIN FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_FS_CR, 0x6)
|
|
+#define AIROHA_USB_PHY_USB20_FS_SR GENMASK(2, 0)
|
|
+#define AIROHA_USB_PHY_USB20_FS_SR_MAX FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_FS_SR, 0x0)
|
|
+#define AIROHA_USB_PHY_USB20_FS_SR_NORMAL FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_FS_SR, 0x2)
|
|
+#define AIROHA_USB_PHY_USB20_FS_SR_SMALLER FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_FS_SR, 0x4)
|
|
+#define AIROHA_USB_PHY_USB20_FS_SR_MIN FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_FS_SR, 0x6)
|
|
+#define AIROHA_USB_PHY_USBPHYACR5 0x314
|
|
+#define AIROHA_USB_PHY_USB20_HSTX_SRCAL_EN BIT(15)
|
|
+#define AIROHA_USB_PHY_USB20_HSTX_SRCTRL GENMASK(14, 12)
|
|
+#define AIROHA_USB_PHY_USBPHYACR6 0x318
|
|
+#define AIROHA_USB_PHY_USB20_BC11_SW_EN BIT(23)
|
|
+#define AIROHA_USB_PHY_USB20_DISCTH GENMASK(7, 4)
|
|
+#define AIROHA_USB_PHY_USB20_DISCTH_400 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_DISCTH, 0x0)
|
|
+#define AIROHA_USB_PHY_USB20_DISCTH_420 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_DISCTH, 0x1)
|
|
+#define AIROHA_USB_PHY_USB20_DISCTH_440 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_DISCTH, 0x2)
|
|
+#define AIROHA_USB_PHY_USB20_DISCTH_460 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_DISCTH, 0x3)
|
|
+#define AIROHA_USB_PHY_USB20_DISCTH_480 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_DISCTH, 0x4)
|
|
+#define AIROHA_USB_PHY_USB20_DISCTH_500 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_DISCTH, 0x5)
|
|
+#define AIROHA_USB_PHY_USB20_DISCTH_520 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_DISCTH, 0x6)
|
|
+#define AIROHA_USB_PHY_USB20_DISCTH_540 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_DISCTH, 0x7)
|
|
+#define AIROHA_USB_PHY_USB20_DISCTH_560 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_DISCTH, 0x8)
|
|
+#define AIROHA_USB_PHY_USB20_DISCTH_580 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_DISCTH, 0x9)
|
|
+#define AIROHA_USB_PHY_USB20_DISCTH_600 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_DISCTH, 0xa)
|
|
+#define AIROHA_USB_PHY_USB20_DISCTH_620 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_DISCTH, 0xb)
|
|
+#define AIROHA_USB_PHY_USB20_DISCTH_640 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_DISCTH, 0xc)
|
|
+#define AIROHA_USB_PHY_USB20_DISCTH_660 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_DISCTH, 0xd)
|
|
+#define AIROHA_USB_PHY_USB20_DISCTH_680 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_DISCTH, 0xe)
|
|
+#define AIROHA_USB_PHY_USB20_DISCTH_700 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_DISCTH, 0xf)
|
|
+#define AIROHA_USB_PHY_USB20_SQTH GENMASK(3, 0)
|
|
+#define AIROHA_USB_PHY_USB20_SQTH_85 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_SQTH, 0x0)
|
|
+#define AIROHA_USB_PHY_USB20_SQTH_90 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_SQTH, 0x1)
|
|
+#define AIROHA_USB_PHY_USB20_SQTH_95 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_SQTH, 0x2)
|
|
+#define AIROHA_USB_PHY_USB20_SQTH_100 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_SQTH, 0x3)
|
|
+#define AIROHA_USB_PHY_USB20_SQTH_105 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_SQTH, 0x4)
|
|
+#define AIROHA_USB_PHY_USB20_SQTH_110 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_SQTH, 0x5)
|
|
+#define AIROHA_USB_PHY_USB20_SQTH_115 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_SQTH, 0x6)
|
|
+#define AIROHA_USB_PHY_USB20_SQTH_120 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_SQTH, 0x7)
|
|
+#define AIROHA_USB_PHY_USB20_SQTH_125 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_SQTH, 0x8)
|
|
+#define AIROHA_USB_PHY_USB20_SQTH_130 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_SQTH, 0x9)
|
|
+#define AIROHA_USB_PHY_USB20_SQTH_135 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_SQTH, 0xa)
|
|
+#define AIROHA_USB_PHY_USB20_SQTH_140 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_SQTH, 0xb)
|
|
+#define AIROHA_USB_PHY_USB20_SQTH_145 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_SQTH, 0xc)
|
|
+#define AIROHA_USB_PHY_USB20_SQTH_150 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_SQTH, 0xd)
|
|
+#define AIROHA_USB_PHY_USB20_SQTH_155 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_SQTH, 0xe)
|
|
+#define AIROHA_USB_PHY_USB20_SQTH_160 FIELD_PREP_CONST(AIROHA_USB_PHY_USB20_SQTH, 0xf)
|
|
+
|
|
+#define AIROHA_USB_PHY_U2PHYDTM1 0x36c
|
|
+#define AIROHA_USB_PHY_FORCE_IDDIG BIT(9)
|
|
+#define AIROHA_USB_PHY_IDDIG BIT(1)
|
|
+
|
|
+#define AIROHA_USB_PHY_GPIO_CTLD 0x80c
|
|
+#define AIROHA_USB_PHY_C60802_GPIO_CTLD GENMASK(31, 0)
|
|
+#define AIROHA_USB_PHY_SSUSB_IP_SW_RST BIT(31)
|
|
+#define AIROHA_USB_PHY_MCU_BUS_CK_GATE_EN BIT(30)
|
|
+#define AIROHA_USB_PHY_FORCE_SSUSB_IP_SW_RST BIT(29)
|
|
+#define AIROHA_USB_PHY_SSUSB_SW_RST BIT(28)
|
|
+
|
|
+#define AIROHA_USB_PHY_U3_PHYA_REG0 0xb00
|
|
+#define AIROHA_USB_PHY_SSUSB_BG_DIV GENMASK(29, 28)
|
|
+#define AIROHA_USB_PHY_SSUSB_BG_DIV_2 FIELD_PREP_CONST(AIROHA_USB_PHY_SSUSB_BG_DIV, 0x0)
|
|
+#define AIROHA_USB_PHY_SSUSB_BG_DIV_4 FIELD_PREP_CONST(AIROHA_USB_PHY_SSUSB_BG_DIV, 0x1)
|
|
+#define AIROHA_USB_PHY_SSUSB_BG_DIV_8 FIELD_PREP_CONST(AIROHA_USB_PHY_SSUSB_BG_DIV, 0x2)
|
|
+#define AIROHA_USB_PHY_SSUSB_BG_DIV_16 FIELD_PREP_CONST(AIROHA_USB_PHY_SSUSB_BG_DIV, 0x3)
|
|
+#define AIROHA_USB_PHY_U3_PHYA_REG1 0xb04
|
|
+#define AIROHA_USB_PHY_SSUSB_XTAL_TOP_RESERVE GENMASK(25, 10)
|
|
+#define AIROHA_USB_PHY_U3_PHYA_REG6 0xb18
|
|
+#define AIROHA_USB_PHY_SSUSB_CDR_RESERVE GENMASK(31, 24)
|
|
+#define AIROHA_USB_PHY_U3_PHYA_REG8 0xb20
|
|
+#define AIROHA_USB_PHY_SSUSB_CDR_RST_DLY GENMASK(7, 6)
|
|
+#define AIROHA_USB_PHY_SSUSB_CDR_RST_DLY_32 FIELD_PREP_CONST(AIROHA_USB_PHY_SSUSB_CDR_RST_DLY, 0x0)
|
|
+#define AIROHA_USB_PHY_SSUSB_CDR_RST_DLY_64 FIELD_PREP_CONST(AIROHA_USB_PHY_SSUSB_CDR_RST_DLY, 0x1)
|
|
+#define AIROHA_USB_PHY_SSUSB_CDR_RST_DLY_128 FIELD_PREP_CONST(AIROHA_USB_PHY_SSUSB_CDR_RST_DLY, 0x2)
|
|
+#define AIROHA_USB_PHY_SSUSB_CDR_RST_DLY_216 FIELD_PREP_CONST(AIROHA_USB_PHY_SSUSB_CDR_RST_DLY, 0x3)
|
|
+
|
|
+#define AIROHA_USB_PHY_U3_PHYA_DA_REG19 0xc38
|
|
+#define AIROHA_USB_PHY_SSUSB_PLL_SSC_DELTA1_U3 GENMASK(15, 0)
|
|
+
|
|
+#define AIROHA_USB_PHY_U2_FM_DET_CYCLE_CNT 1024
|
|
+#define AIROHA_USB_PHY_REF_CK 20
|
|
+#define AIROHA_USB_PHY_U2_SR_COEF 28
|
|
+#define AIROHA_USB_PHY_U2_SR_COEF_DIVISOR 1000
|
|
+
|
|
+#define AIROHA_USB_PHY_DEFAULT_SR_CALIBRATION 0x5
|
|
+#define AIROHA_USB_PHY_FREQDET_SLEEP 1000 /* 1ms */
|
|
+#define AIROHA_USB_PHY_FREQDET_TIMEOUT (AIROHA_USB_PHY_FREQDET_SLEEP * 10)
|
|
+
|
|
+struct airoha_usb_phy_instance {
|
|
+ struct phy *phy;
|
|
+ u32 type;
|
|
+};
|
|
+
|
|
+enum airoha_usb_phy_instance_type {
|
|
+ AIROHA_PHY_USB2,
|
|
+ AIROHA_PHY_USB3,
|
|
+
|
|
+ AIROHA_PHY_USB_MAX,
|
|
+};
|
|
+
|
|
+struct airoha_usb_phy_priv {
|
|
+ struct device *dev;
|
|
+ struct regmap *regmap;
|
|
+ struct regmap *scu;
|
|
+
|
|
+ unsigned int monclk_sel;
|
|
+ unsigned int serdes_port;
|
|
+
|
|
+ struct airoha_usb_phy_instance *phys[AIROHA_PHY_USB_MAX];
|
|
+};
|
|
+
|
|
+static void airoha_usb_phy_u2_slew_rate_calibration(struct airoha_usb_phy_priv *priv)
|
|
+{
|
|
+ u32 fm_out;
|
|
+ u32 srctrl;
|
|
+
|
|
+ /* Enable HS TX SR calibration */
|
|
+ regmap_set_bits(priv->regmap, AIROHA_USB_PHY_USBPHYACR5,
|
|
+ AIROHA_USB_PHY_USB20_HSTX_SRCAL_EN);
|
|
+
|
|
+ usleep_range(1000, 1500);
|
|
+
|
|
+ /* Enable Free run clock */
|
|
+ regmap_set_bits(priv->regmap, AIROHA_USB_PHY_FMMONR1,
|
|
+ AIROHA_USB_PHY_FRCK_EN);
|
|
+
|
|
+ /* Select Monitor Clock */
|
|
+ regmap_update_bits(priv->regmap, AIROHA_USB_PHY_FMCR0,
|
|
+ AIROHA_USB_PHY_MONCLK_SEL,
|
|
+ FIELD_PREP(AIROHA_USB_PHY_MONCLK_SEL,
|
|
+ priv->monclk_sel));
|
|
+
|
|
+ /* Set cyclecnt */
|
|
+ regmap_update_bits(priv->regmap, AIROHA_USB_PHY_FMCR0,
|
|
+ AIROHA_USB_PHY_CYCLECNT,
|
|
+ FIELD_PREP(AIROHA_USB_PHY_CYCLECNT,
|
|
+ AIROHA_USB_PHY_U2_FM_DET_CYCLE_CNT));
|
|
+
|
|
+ /* Enable Frequency meter */
|
|
+ regmap_set_bits(priv->regmap, AIROHA_USB_PHY_FMCR0,
|
|
+ AIROHA_USB_PHY_FREQDET_EN);
|
|
+
|
|
+ /* Timeout can happen and we will apply workaround at the end */
|
|
+ regmap_read_poll_timeout(priv->regmap, AIROHA_USB_PHY_FMMONR0, fm_out,
|
|
+ fm_out, AIROHA_USB_PHY_FREQDET_SLEEP,
|
|
+ AIROHA_USB_PHY_FREQDET_TIMEOUT);
|
|
+
|
|
+ /* Disable Frequency meter */
|
|
+ regmap_clear_bits(priv->regmap, AIROHA_USB_PHY_FMCR0,
|
|
+ AIROHA_USB_PHY_FREQDET_EN);
|
|
+
|
|
+ /* Disable Free run clock */
|
|
+ regmap_clear_bits(priv->regmap, AIROHA_USB_PHY_FMMONR1,
|
|
+ AIROHA_USB_PHY_FRCK_EN);
|
|
+
|
|
+ /* Disable HS TX SR calibration */
|
|
+ regmap_clear_bits(priv->regmap, AIROHA_USB_PHY_USBPHYACR5,
|
|
+ AIROHA_USB_PHY_USB20_HSTX_SRCAL_EN);
|
|
+
|
|
+ usleep_range(1000, 1500);
|
|
+
|
|
+ /* Frequency was not detected, use default SR calibration value */
|
|
+ if (!fm_out) {
|
|
+ srctrl = AIROHA_USB_PHY_DEFAULT_SR_CALIBRATION;
|
|
+ dev_err(priv->dev, "Frequency not detected, using default SR calibration.\n");
|
|
+ } else {
|
|
+ /* (1024 / FM_OUT) * REF_CK * U2_SR_COEF (round to the nearest digits) */
|
|
+ srctrl = AIROHA_USB_PHY_REF_CK * AIROHA_USB_PHY_U2_SR_COEF;
|
|
+ srctrl = (srctrl * AIROHA_USB_PHY_U2_FM_DET_CYCLE_CNT) / fm_out;
|
|
+ srctrl = DIV_ROUND_CLOSEST(srctrl, AIROHA_USB_PHY_U2_SR_COEF_DIVISOR);
|
|
+ dev_dbg(priv->dev, "SR calibration applied: %x\n", srctrl);
|
|
+ }
|
|
+
|
|
+ regmap_update_bits(priv->regmap, AIROHA_USB_PHY_USBPHYACR5,
|
|
+ AIROHA_USB_PHY_USB20_HSTX_SRCTRL,
|
|
+ FIELD_PREP(AIROHA_USB_PHY_USB20_HSTX_SRCTRL, srctrl));
|
|
+}
|
|
+
|
|
+static void airoha_usb_phy_u2_init(struct airoha_usb_phy_priv *priv)
|
|
+{
|
|
+ regmap_update_bits(priv->regmap, AIROHA_USB_PHY_USBPHYACR4,
|
|
+ AIROHA_USB_PHY_USB20_FS_CR,
|
|
+ AIROHA_USB_PHY_USB20_FS_CR_MIN);
|
|
+
|
|
+ regmap_update_bits(priv->regmap, AIROHA_USB_PHY_USBPHYACR4,
|
|
+ AIROHA_USB_PHY_USB20_FS_SR,
|
|
+ AIROHA_USB_PHY_USB20_FS_SR_NORMAL);
|
|
+
|
|
+ /* FIXME: evaluate if needed */
|
|
+ regmap_update_bits(priv->regmap, AIROHA_USB_PHY_USBPHYACR6,
|
|
+ AIROHA_USB_PHY_USB20_SQTH,
|
|
+ AIROHA_USB_PHY_USB20_SQTH_130);
|
|
+
|
|
+ regmap_update_bits(priv->regmap, AIROHA_USB_PHY_USBPHYACR6,
|
|
+ AIROHA_USB_PHY_USB20_DISCTH,
|
|
+ AIROHA_USB_PHY_USB20_DISCTH_600);
|
|
+
|
|
+ /* Enable the USB port and then disable after calibration */
|
|
+ regmap_clear_bits(priv->regmap, AIROHA_USB_PHY_USBPHYACR6,
|
|
+ AIROHA_USB_PHY_USB20_BC11_SW_EN);
|
|
+
|
|
+ airoha_usb_phy_u2_slew_rate_calibration(priv);
|
|
+
|
|
+ regmap_set_bits(priv->regmap, AIROHA_USB_PHY_USBPHYACR6,
|
|
+ AIROHA_USB_PHY_USB20_BC11_SW_EN);
|
|
+
|
|
+ usleep_range(1000, 1500);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * USB 3.0 mode can only work if USB serdes is correctly set.
|
|
+ * This is validated in xLate function.
|
|
+ */
|
|
+static void airoha_usb_phy_u3_init(struct airoha_usb_phy_priv *priv)
|
|
+{
|
|
+ regmap_update_bits(priv->regmap, AIROHA_USB_PHY_U3_PHYA_REG8,
|
|
+ AIROHA_USB_PHY_SSUSB_CDR_RST_DLY,
|
|
+ AIROHA_USB_PHY_SSUSB_CDR_RST_DLY_32);
|
|
+
|
|
+ regmap_update_bits(priv->regmap, AIROHA_USB_PHY_U3_PHYA_REG6,
|
|
+ AIROHA_USB_PHY_SSUSB_CDR_RESERVE,
|
|
+ FIELD_PREP(AIROHA_USB_PHY_SSUSB_CDR_RESERVE, 0xe));
|
|
+
|
|
+ regmap_update_bits(priv->regmap, AIROHA_USB_PHY_U3_PHYA_REG0,
|
|
+ AIROHA_USB_PHY_SSUSB_BG_DIV,
|
|
+ AIROHA_USB_PHY_SSUSB_BG_DIV_4);
|
|
+
|
|
+ regmap_set_bits(priv->regmap, AIROHA_USB_PHY_U3_PHYA_REG1,
|
|
+ FIELD_PREP(AIROHA_USB_PHY_SSUSB_XTAL_TOP_RESERVE, 0x600));
|
|
+
|
|
+ regmap_update_bits(priv->regmap, AIROHA_USB_PHY_U3_PHYA_DA_REG19,
|
|
+ AIROHA_USB_PHY_SSUSB_PLL_SSC_DELTA1_U3,
|
|
+ FIELD_PREP(AIROHA_USB_PHY_SSUSB_PLL_SSC_DELTA1_U3, 0x43));
|
|
+}
|
|
+
|
|
+static int airoha_usb_phy_init(struct phy *phy)
|
|
+{
|
|
+ struct airoha_usb_phy_instance *instance = phy_get_drvdata(phy);
|
|
+ struct airoha_usb_phy_priv *priv = dev_get_drvdata(phy->dev.parent);
|
|
+
|
|
+ switch (instance->type) {
|
|
+ case PHY_TYPE_USB2:
|
|
+ airoha_usb_phy_u2_init(priv);
|
|
+ break;
|
|
+ case PHY_TYPE_USB3:
|
|
+ if (phy_get_mode(phy) == PHY_MODE_PCIE)
|
|
+ return 0;
|
|
+
|
|
+ airoha_usb_phy_u3_init(priv);
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int airoha_usb_phy_u2_power_on(struct airoha_usb_phy_priv *priv)
|
|
+{
|
|
+ regmap_clear_bits(priv->regmap, AIROHA_USB_PHY_USBPHYACR6,
|
|
+ AIROHA_USB_PHY_USB20_BC11_SW_EN);
|
|
+
|
|
+ usleep_range(1000, 1500);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int airoha_usb_phy_u3_power_on(struct airoha_usb_phy_priv *priv)
|
|
+{
|
|
+ regmap_clear_bits(priv->regmap, AIROHA_USB_PHY_GPIO_CTLD,
|
|
+ AIROHA_USB_PHY_SSUSB_IP_SW_RST |
|
|
+ AIROHA_USB_PHY_MCU_BUS_CK_GATE_EN |
|
|
+ AIROHA_USB_PHY_FORCE_SSUSB_IP_SW_RST |
|
|
+ AIROHA_USB_PHY_SSUSB_SW_RST);
|
|
+
|
|
+ usleep_range(1000, 1500);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int airoha_usb_phy_power_on(struct phy *phy)
|
|
+{
|
|
+ struct airoha_usb_phy_instance *instance = phy_get_drvdata(phy);
|
|
+ struct airoha_usb_phy_priv *priv = dev_get_drvdata(phy->dev.parent);
|
|
+
|
|
+ switch (instance->type) {
|
|
+ case PHY_TYPE_USB2:
|
|
+ airoha_usb_phy_u2_power_on(priv);
|
|
+ break;
|
|
+ case PHY_TYPE_USB3:
|
|
+ if (phy_get_mode(phy) == PHY_MODE_PCIE)
|
|
+ return 0;
|
|
+
|
|
+ airoha_usb_phy_u3_power_on(priv);
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int airoha_usb_phy_u2_power_off(struct airoha_usb_phy_priv *priv)
|
|
+{
|
|
+ regmap_set_bits(priv->regmap, AIROHA_USB_PHY_USBPHYACR6,
|
|
+ AIROHA_USB_PHY_USB20_BC11_SW_EN);
|
|
+
|
|
+ usleep_range(1000, 1500);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int airoha_usb_phy_u3_power_off(struct airoha_usb_phy_priv *priv)
|
|
+{
|
|
+ regmap_set_bits(priv->regmap, AIROHA_USB_PHY_GPIO_CTLD,
|
|
+ AIROHA_USB_PHY_SSUSB_IP_SW_RST |
|
|
+ AIROHA_USB_PHY_FORCE_SSUSB_IP_SW_RST);
|
|
+
|
|
+ usleep_range(1000, 1500);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int airoha_usb_phy_power_off(struct phy *phy)
|
|
+{
|
|
+ struct airoha_usb_phy_instance *instance = phy_get_drvdata(phy);
|
|
+ struct airoha_usb_phy_priv *priv = dev_get_drvdata(phy->dev.parent);
|
|
+
|
|
+ switch (instance->type) {
|
|
+ case PHY_TYPE_USB2:
|
|
+ airoha_usb_phy_u2_power_off(priv);
|
|
+ break;
|
|
+ case PHY_TYPE_USB3:
|
|
+ if (phy_get_mode(phy) == PHY_MODE_PCIE)
|
|
+ return 0;
|
|
+
|
|
+ airoha_usb_phy_u3_power_off(priv);
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int airoha_usb_phy_u2_set_mode(struct airoha_usb_phy_priv *priv,
|
|
+ enum phy_mode mode)
|
|
+{
|
|
+ u32 val;
|
|
+
|
|
+ /*
|
|
+ * For Device and Host mode, enable force IDDIG.
|
|
+ * For Device set IDDIG, for Host clear IDDIG.
|
|
+ * For OTG disable force and clear IDDIG bit while at it.
|
|
+ */
|
|
+ switch (mode) {
|
|
+ case PHY_MODE_USB_DEVICE:
|
|
+ val = AIROHA_USB_PHY_IDDIG;
|
|
+ break;
|
|
+ case PHY_MODE_USB_HOST:
|
|
+ val = AIROHA_USB_PHY_FORCE_IDDIG |
|
|
+ AIROHA_USB_PHY_FORCE_IDDIG;
|
|
+ break;
|
|
+ case PHY_MODE_USB_OTG:
|
|
+ val = 0;
|
|
+ break;
|
|
+ default:
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ regmap_update_bits(priv->regmap, AIROHA_USB_PHY_U2PHYDTM1,
|
|
+ AIROHA_USB_PHY_FORCE_IDDIG |
|
|
+ AIROHA_USB_PHY_IDDIG, val);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int airoha_usb_phy_u3_set_mode(struct airoha_usb_phy_priv *priv,
|
|
+ enum phy_mode mode)
|
|
+{
|
|
+ u32 sel;
|
|
+
|
|
+ /* Only USB2 supports PCIe mode */
|
|
+ if (mode == PHY_MODE_PCIE &&
|
|
+ priv->serdes_port != AIROHA_SCU_SERDES_USB2)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (mode == PHY_MODE_PCIE)
|
|
+ sel = AIROHA_SCU_SSTR_USB_PCIE_SEL_PCIE;
|
|
+ else
|
|
+ sel = AIROHA_SCU_SSTR_USB_PCIE_SEL_USB;
|
|
+
|
|
+ regmap_update_bits(priv->scu, AIROHA_SCU_SSTR,
|
|
+ AIROHA_SCU_SSTR_USB_PCIE_SEL, sel);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int airoha_usb_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode)
|
|
+{
|
|
+ struct airoha_usb_phy_instance *instance = phy_get_drvdata(phy);
|
|
+ struct airoha_usb_phy_priv *priv = dev_get_drvdata(phy->dev.parent);
|
|
+
|
|
+ switch (instance->type) {
|
|
+ case PHY_TYPE_USB2:
|
|
+ return airoha_usb_phy_u2_set_mode(priv, mode);
|
|
+ case PHY_TYPE_USB3:
|
|
+ return airoha_usb_phy_u3_set_mode(priv, mode);
|
|
+ default:
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static struct phy *airoha_usb_phy_xlate(struct device *dev,
|
|
+ const struct of_phandle_args *args)
|
|
+{
|
|
+ struct airoha_usb_phy_priv *priv = dev_get_drvdata(dev);
|
|
+ struct airoha_usb_phy_instance *instance = NULL;
|
|
+ unsigned int index, phy_type;
|
|
+
|
|
+ if (args->args_count != 1) {
|
|
+ dev_err(dev, "invalid number of cells in 'phy' property\n");
|
|
+ return ERR_PTR(-EINVAL);
|
|
+ }
|
|
+
|
|
+ phy_type = args->args[0];
|
|
+ if (!(phy_type == PHY_TYPE_USB2 || phy_type == PHY_TYPE_USB3)) {
|
|
+ dev_err(dev, "unsupported device type: %d\n", phy_type);
|
|
+ return ERR_PTR(-EINVAL);
|
|
+ }
|
|
+
|
|
+ for (index = 0; index < AIROHA_PHY_USB_MAX; index++)
|
|
+ if (priv->phys[index] &&
|
|
+ phy_type == priv->phys[index]->type) {
|
|
+ instance = priv->phys[index];
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (!instance) {
|
|
+ dev_err(dev, "failed to find appropriate phy\n");
|
|
+ return ERR_PTR(-EINVAL);
|
|
+ }
|
|
+
|
|
+ return instance->phy;
|
|
+}
|
|
+
|
|
+static const struct phy_ops airoha_phy = {
|
|
+ .init = airoha_usb_phy_init,
|
|
+ .power_on = airoha_usb_phy_power_on,
|
|
+ .power_off = airoha_usb_phy_power_off,
|
|
+ .set_mode = airoha_usb_phy_set_mode,
|
|
+ .owner = THIS_MODULE,
|
|
+};
|
|
+
|
|
+static const struct regmap_config airoha_usb_phy_regmap_config = {
|
|
+ .reg_bits = 32,
|
|
+ .val_bits = 32,
|
|
+ .reg_stride = 4,
|
|
+};
|
|
+
|
|
+static int airoha_usb_phy_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct phy_provider *phy_provider;
|
|
+ struct airoha_usb_phy_priv *priv;
|
|
+ struct device *dev = &pdev->dev;
|
|
+ unsigned int index;
|
|
+ void *base;
|
|
+ int ret;
|
|
+
|
|
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
|
+ if (!priv)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ priv->dev = dev;
|
|
+
|
|
+ ret = of_property_read_u32(dev->of_node, "airoha,usb2-monitor-clk-sel",
|
|
+ &priv->monclk_sel);
|
|
+ if (ret)
|
|
+ return dev_err_probe(dev, ret, "Monitor clock selection is mandatory for USB PHY calibration.\n");
|
|
+
|
|
+ if (priv->monclk_sel > 3)
|
|
+ return dev_err_probe(dev, -EINVAL, "only 4 Monitor clock are selectable on the SoC.\n");
|
|
+
|
|
+ base = devm_platform_ioremap_resource(pdev, 0);
|
|
+ if (IS_ERR(base))
|
|
+ return PTR_ERR(base);
|
|
+
|
|
+ priv->regmap = devm_regmap_init_mmio(dev, base, &airoha_usb_phy_regmap_config);
|
|
+ if (IS_ERR(priv->regmap))
|
|
+ return PTR_ERR(priv->regmap);
|
|
+
|
|
+ platform_set_drvdata(pdev, priv);
|
|
+
|
|
+ for (index = 0; index < AIROHA_PHY_USB_MAX; index++) {
|
|
+ enum airoha_usb_phy_instance_type phy_type;
|
|
+ struct airoha_usb_phy_instance *instance;
|
|
+
|
|
+ switch (index) {
|
|
+ case AIROHA_PHY_USB2:
|
|
+ phy_type = PHY_TYPE_USB2;
|
|
+ break;
|
|
+ case AIROHA_PHY_USB3:
|
|
+ phy_type = PHY_TYPE_USB3;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* Skip registering USB3 instance if not supported */
|
|
+ if (phy_type == PHY_TYPE_USB3) {
|
|
+ ret = of_property_read_u32(dev->of_node, "airoha,serdes-port",
|
|
+ &priv->serdes_port);
|
|
+ if (ret)
|
|
+ continue;
|
|
+
|
|
+ /* With Serdes Port property, SCU is required */
|
|
+ priv->scu = syscon_regmap_lookup_by_phandle(dev->of_node,
|
|
+ "airoha,scu");
|
|
+ if (IS_ERR(priv->scu))
|
|
+ return dev_err_probe(dev, PTR_ERR(priv->scu), "failed to get SCU syscon.\n");
|
|
+ }
|
|
+
|
|
+ instance = devm_kzalloc(dev, sizeof(*instance), GFP_KERNEL);
|
|
+ if (!instance)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ instance->type = phy_type;
|
|
+ priv->phys[index] = instance;
|
|
+
|
|
+ instance->phy = devm_phy_create(dev, NULL, &airoha_phy);
|
|
+ if (IS_ERR(instance->phy))
|
|
+ return dev_err_probe(dev, PTR_ERR(instance->phy), "failed to create phy\n");
|
|
+
|
|
+ phy_set_drvdata(instance->phy, instance);
|
|
+ }
|
|
+
|
|
+ phy_provider = devm_of_phy_provider_register(&pdev->dev, airoha_usb_phy_xlate);
|
|
+
|
|
+ return PTR_ERR_OR_ZERO(phy_provider);
|
|
+}
|
|
+
|
|
+static const struct of_device_id airoha_phy_id_table[] = {
|
|
+ { .compatible = "airoha,an7581-usb-phy" },
|
|
+ { },
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, airoha_phy_id_table);
|
|
+
|
|
+static struct platform_driver airoha_usb_driver = {
|
|
+ .probe = airoha_usb_phy_probe,
|
|
+ .driver = {
|
|
+ .name = "airoha-usb-phy",
|
|
+ .of_match_table = airoha_phy_id_table,
|
|
+ },
|
|
+};
|
|
+
|
|
+module_platform_driver(airoha_usb_driver);
|
|
+
|
|
+MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_DESCRIPTION("Airoha USB PHY driver");
|