From 423f4afd3392d6933c002abb10ebcb0d75db9ad3 Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Mon, 3 Aug 2026 20:47:22 +0000 Subject: [PATCH 1/9] phy: allwinner: Introduce Combo PHY driver This is imported from Allwinner SDK as-is, only with compilation errors fixes. Signed-off-by: Yao Zi --- drivers/phy/allwinner/Kconfig | 9 + drivers/phy/allwinner/Makefile | 1 + .../phy/allwinner/sunxi-cadence-combophy.c | 4370 +++++++++++++++++ 3 files changed, 4380 insertions(+) create mode 100644 drivers/phy/allwinner/sunxi-cadence-combophy.c diff --git a/drivers/phy/allwinner/Kconfig b/drivers/phy/allwinner/Kconfig index fb584518b2d0fd..37e7d7c836e009 100644 --- a/drivers/phy/allwinner/Kconfig +++ b/drivers/phy/allwinner/Kconfig @@ -57,3 +57,12 @@ config PHY_SUN50I_USB3 part of Allwinner H6 SoC. This driver controls each individual USB 2+3 host PHY combo. + +config PHY_SUN60I_COMBOPHY + tristate "Allwinner Cadence Combo PHY driver for A733" + depends on OF + select GENERIC_PHY + select EXTCON + help + Enable this to support the Cadence Combo PHY found on Allwinner + A733 SoCs. diff --git a/drivers/phy/allwinner/Makefile b/drivers/phy/allwinner/Makefile index bd74901a12551e..bfa3cdd3ac44dd 100644 --- a/drivers/phy/allwinner/Makefile +++ b/drivers/phy/allwinner/Makefile @@ -3,3 +3,4 @@ obj-$(CONFIG_PHY_SUN4I_USB) += phy-sun4i-usb.o obj-$(CONFIG_PHY_SUN6I_MIPI_DPHY) += phy-sun6i-mipi-dphy.o obj-$(CONFIG_PHY_SUN9I_USB) += phy-sun9i-usb.o obj-$(CONFIG_PHY_SUN50I_USB3) += phy-sun50i-usb3.o +obj-$(CONFIG_PHY_SUN60I_COMBOPHY) += sunxi-cadence-combophy.o diff --git a/drivers/phy/allwinner/sunxi-cadence-combophy.c b/drivers/phy/allwinner/sunxi-cadence-combophy.c new file mode 100644 index 00000000000000..70c3a6df9b8573 --- /dev/null +++ b/drivers/phy/allwinner/sunxi-cadence-combophy.c @@ -0,0 +1,4370 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2015-2025 Allwinner Technology Co., Ltd. All rights reserved. */ +/* + * Allwinner USB3.0/PCIE/DisplayPort Combo Phy driver + * + * Copyright (C) 2015-2025 Allwinner Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct phy *sunxi_cadence_phy_xlate(struct device *dev, + const struct of_phandle_args *args); + +#define phy_set_mask(width, shift) ((width?((-1U) >> (32-width)):0) << (shift)) +#define phy_clear_mask(width, shift) (~(phy_set_mask(width, shift))) + +#define phy_readl(addr) readl(addr) +#define phy_readw(addr) readw(addr) +#define phy_writel(addr, val) writel(val, addr) +#define phy_writew(addr, val) writew(val, addr) + +/* The mode record which combo phy xlate owner */ +#define COMBO_PHY_TYPE(mode, type) (mode & ((1 << type))) + +#define MAX_LANE_CNT 4 +#define MAX_LANE_NO 3 +#define MAX_STATE_D_F_LANE_CNT 2 +#define MAX_SWING 4 +#define MAX_PRE 4 + +/** + * NOTE: + * External PHY mode: + * bit[1:0] - orientation, + * bit[5:2] - mode + */ +#define COMBO0_TYPEC_ORIENTATION(n) (n & GENMASK(1, 0)) +#define COMBO0_TYPEC_MODE(n) ((n & GENMASK(5, 2)) >> 0x2) +#define COMBO0_HPD_STATE(n) ((n & BIT(6)) >> 0x6) + +static const char * const typec_orientation_name[] = { + [TYPEC_ORIENTATION_NONE] = "UNKNOW", + [TYPEC_ORIENTATION_NORMAL] = "NORMAL", + [TYPEC_ORIENTATION_REVERSE] = "REVERSE", +}; + +static const char * const typec_state_name[] = { + [TYPEC_STATE_SAFE] = "STATE_SAFE", + [TYPEC_STATE_USB] = "STATE_USB", + [TYPEC_DP_STATE_C] = "STATE_DP_C", + [TYPEC_DP_STATE_D] = "STATE_DP_D", + [TYPEC_DP_STATE_E] = "STATE_DP_E", + [TYPEC_DP_STATE_F] = "STATE_DP_F", +}; + +static const unsigned int sunxi_cadence_cable[] = { + EXTCON_DISP_DP, + EXTCON_USB, + EXTCON_NONE, +}; + +static void phy_set_lbits(void *addr, u32 shift, u32 width, u32 val) +{ + u32 reg_val; + + reg_val = phy_readl(addr); + reg_val &= phy_clear_mask(width, shift); + reg_val |= (val << shift); + phy_writel(addr, reg_val); +} + +struct sunxi_cadence_combophy { + const char *name; + void __iomem *top_reg; + void __iomem *phy_reg; + struct clk *clk; + struct clk *bus_clk; + struct reset_control *reset; + enum typec_orientation orientation; + struct sunxi_cadence_phy *sunxi_cphy; + struct mutex phy_lock; + + unsigned long state; + bool usb_dp_state; + __u8 hpd_state; + __u8 ssc_en; + __u32 mode; + __u8 configuration; + __u32 vernum; /* COMB0/1 PHY Version number */ + __u32 lane_remap[MAX_LANE_CNT]; + __u32 typec_remap[MAX_LANE_CNT]; + __u32 lane_invert[MAX_LANE_CNT]; + unsigned int link_rate; +}; + +struct sunxi_cadence_phy { + void __iomem *top_subsys_reg; + void __iomem *top_combo_reg; + struct device *dev; + struct clk *serdes_clk; + struct clk *dcxo_serdes0_clk; + struct clk *dcxo_serdes1_clk; + struct reset_control *serdes_reset; + struct regulator *avdd_h_regulator; + struct regulator *avdd_c_regulator; + struct regulator *avdd_d_regulator; + struct regulator *avdd_aux_regulator; + struct extcon_dev *extcon; + + struct sunxi_cadence_combophy *combo0; + struct sunxi_cadence_combophy *combo1; + struct sunxi_cadence_combophy *aux_hpd; + + __u32 vernum; /* SUBSYS TOP Version number */ + bool usb_supported; /* USB can use combo0/1 or support only U3/U2 mode */ + bool udp_supported; /* Both USB and DP can use combo0 meanwhile */ + bool phy_switcher_quirk; + + struct regulator *serdes1v8_supply; +}; + +enum phy_type_e { + COMBO_PHY0 = 0, + COMBO_PHY1, + AUX_HPD, +}; + +enum phy_config_e { + PHY_CFG_SSC = 0x1, +}; + +static int combo0_configure_usb_dp(struct sunxi_cadence_combophy *combo0); + +/* Sub-System PCIE Bus Gating Reset Register */ +#define SUBSYS_PCIE_BGR 0x0004 +#define SUBSYS_PCIE_GATING (BIT(16) | BIT(17) | BIT(18)) +#define SUBSYS_DISABLE_COMBO1_AUTOGATING BIT(29) +#define SUBSYS_COMB1_PIPE 0xc44 +#define SUBSYS_COMB1_PIPE_PCIE 0x1 + +/* Sub-System USB3.1 Bus Gating Reset Register */ +#define SUBSYS_USB3P1_BGR 0x0008 +#define USB3P1_ONLY_UTMI_CLK_SEL BIT(21) +#define USB3P1_ONLY_PIPE_CLK_SEL BIT(20) +#define USB3P1_ACLK_EN BIT(17) +#define USB3P1_HCLK_EN BIT(16) +#define USB3P1_USB2P0_PHY_RSTN BIT(4) + +/* Sub-System DBG CTRL Register */ +#define SUBSYS_DBG_CTL 0x00f0 +#define COMBO1_PHY_REFCLK_AUTO_GATE_DIS BIT(29) +#define COMBO0_PHY_REFCLK_AUTO_GATE_DIS BIT(28) + +/* COMB0 PHY PINS Link Control Register0 */ +#define COMB0_PHY_PINS_LINK_CTRL0 0x0000 +#define PHY0_TYPEC_CONN_DIR BIT(12) /* 0: normal orientation; 1: flipped orientation */ +#define PHY0_DP_LINK_RESET_N_SOFT BIT(8) +#define PHY0_PIPE_LINK_RESET_N_USB3P1_MASK BIT(5) +#define PHY0_PIPE_LINK_RESET_N_SOFT BIT(4) +#define PHY0_RESET_N BIT(0) + +/* COMB0 PHY PINS Link Control Register1 */ +#define COMB0_PHY_PINS_LINK_CTRL1 0x0004 +#define PHY0_PMA_CMN_PLL1_REFCLK_SEL BIT(24) +#define PHY0_PMA_CMN_PLL0_REFCLK_SEL BIT(20) +#define PHY0_PMA_CMN_REFCLK_TERM_EN BIT(16) +#define PHY0_PMA_CMN_REFCLK_DIG_DIV_MASK GENMASK(13, 12) +#define PHY0_PMA_CMN_REFCLK_DIG_DIV(n) (((n) & 0x3) << 12) +#define PHY0_PMA_CMN_REFCLK_INT_MODE_MASK GENMASK(9, 8) +#define PHY0_PMA_CMN_REFCLK_INT_MODE(n) (((n) & 0x3) << 8) +#define PHY0_PMA_CMN_REFCLK_MODE_MASK GENMASK(5, 4) +#define PHY0_PMA_CMN_REFCLK_MODE(n) (((n) & 0x3) << 4) +#define PHY0_PMA_CMN_REFCLK_DIG_SEL BIT(0) + +/* COMB0 PHY PINS Link Control Register2 */ +#define COMB0_PHY_PINS_LINK_CTRL2 0x0008 +#define PHY0_XCVR_BSCAN_MODE_EN_SEL BIT(21) +#define PHY0_XCVR_BSCAN_MODE_EN_SOFT BIT(20) +#define PHY0_BSCAN_EXT_SELECT BIT(16) +#define PHY0_DBG_RX_RD_LANE_SEL_MASK GENMASK(6, 4) +#define PHY0_DBG_RX_RD_LANE_SEL(n) (((n) & 0x7) << 4) +#define PHY0_IDDQ_EN_SEL BIT(1) +#define PHY0_IDDQ_EN_SOFT BIT(0) + +/* COMB0 PHY PINS Link Control Register3 */ +#define COMB0_PHY_PINS_LINK_CTRL3 0x000C +#define PHY0_PMA_TX_ELEC_IDLE_LN_3 BIT(15) +#define PHY0_PMA_TX_ELEC_IDLE_LN_2 BIT(14) +#define PHY0_PMA_TX_ELEC_IDLE_LN_1 BIT(13) +#define PHY0_PMA_TX_ELEC_IDLE_LN_0 BIT(12) +#define PHY0_PMA_XCVR_POWER_STATE_REQ_LN_MASK GENMASK(9, 4) +#define PHY0_PMA_XCVR_POWER_STATE_REQ_LN(n) (((n) & 0x3f) << 4) +#define PHY0_PMA_XCVR_PLLCLK_EN_LN BIT(0) + +/* COMB0 PHY PINS Link Status Register0 */ +#define COMB0_PHY_PINS_LINK_STUS0 0x0900 +#define PHY0_LINK_POWER_STATE_ACK_MASK GENMASK(13, 8) +#define PHY0_LINK_POWER_STATE_ACK(n) (((n) & 0x3f) << 8) +#define PHY0_LINK_CLOCK_EN_ACK BIT(4) +#define PHY0_PMA_CMN_READY BIT(0) + +/* COMB1 PHY PINS Link Control Register0 */ +#define COMB1_PHY_PINS_LINK_CTRL0 0x0000 +#define PHY1_LINK_CFG_LN_MASK GENMASK(6, 4) +#define PHY1_LINK_CFG_LN(n) (((n) & 0x7) << 4) +#define PHY1_RESET_N BIT(0) + +/* COMB1 PHY PINS Link Control Register1 */ +#define COMB1_PHY_PINS_LINK_CTRL1 0x0004 +#define PHY1_EN_REFCLK BIT(28) +#define PHY1_PMA_CMN_PLL1_REFCLK_SEL BIT(24) +#define PHY1_PMA_CMN_PLL0_REFCLK_SEL BIT(20) +#define PHY1_PMA_CMN_REFCLK_TERM_EN BIT(16) +#define PHY1_PMA_CMN_REFCLK_DIG_DIV_MASK GENMASK(13, 12) +#define PHY1_PMA_CMN_REFCLK_DIG_DIV(n) (((n) & 0x3) << 12) +#define PHY1_PMA_CMN_REFCLK_INT_MODE_MASK GENMASK(9, 8) +#define PHY1_PMA_CMN_REFCLK_INT_MODE(n) (((n) & 0x3) << 8) +#define PHY1_PMA_CMN_REFCLK_MODE_MASK GENMASK(5, 4) +#define PHY1_PMA_CMN_REFCLK_MODE(n) (((n) & 0x3) << 4) +#define PHY1_PMA_CMN_REFCLK_DIG_SEL BIT(0) + +/* COMB1 PHY PINS Link Control Register3 */ +#define COMB1_PHY_PINS_LINK_CTRL3 0x000C +#define PHY1_XCVR_BSCAN_MODE_EN BIT(20) +#define PHY1_BSCAN_EXT_SELECT BIT(16) +#define PHY1_DTB_MUX_SELECTE_MASK GENMASK(12, 8) +#define PHY1_DTB_MUX_SELECTE(n) (((n) & 0x1f) << 8) +#define PHY1_IDDQ_EN_SEL BIT(1) +#define PHY1_IDDQ_EN BIT(0) + +/* COMB1 PHY PINS Lane0 Control Register1 */ +#define COMB1_PHY_PINS_LANE0_CTRL0 0x0100 +#define PHY1_L00_ENT_L1_X BIT(16) +#define PHY1_L00_PCIE_L1_SS_SEL BIT(12) +#define PHY1_PMA_FULLRT_DIV_LN_0_MASK GENMASK(9, 8) +#define PHY1_PMA_FULLRT_DIV_LN_0(n) (((n) & 0x3) << 8) +#define PHY1_L00_MODE_MASK GENMASK(5, 4) +#define PHY1_L00_MODE(n) (((n) & 0x3) << 4) +#define PHY1_L00_RESET_N_HW_DIS BIT(1) +#define PHY1_L00_RESET_N_SW BIT(0) + +/* COMB1 PHY PINS Link Status Register0 */ +#define COMB1_PHY_PINS_LINK_STUS0 0x0900 +#define PHY1_PMA_CMN_CLOCK_STOP_ACK BIT(8) +#define PHY1_PMA_CMN_EXT_REFCLK_DETECTED_VALID BIT(5) +#define PHY1_PMA_CMN_EXT_REFCLK_DETECTED BIT(4) +#define PHY1_PMA_CMN_READY BIT(0) + +/* COMBO0 DP RAWS TXDATA CONVERTION CTRL Register */ +#define DP_RAWS_TXDW_CONV_CTRL 0x0004 +#define DP_PHY_CFG_DATA_EN_BIT BIT(0) +#define DP_PHY_CFG_LN_EN_MASK GENMASK(7, 4) +#define DP_PHY_CFG_LN_RSTN_MASK GENMASK(11, 8) + +/* USB3.1 PIPE CLK MAP CTRL Register */ +#define USB3P1_PIPE_CLK_MAP_CTRL 0x0100 +#define USB3P1_PIPE_CLK_MAP_MASK GENMASK(3, 0) +#define USB3P1_PIPE_CLK_MAP(n) (((n) & 0xf)) /* 0x0: PHY0 Lane0 or Lane3; 0x1: PHY1 Lane0 */ + +/* USB3.1 PIPE RXDATA MAP CTRL Register */ +#define USB3P1_PIPE_RXD_MAP_CTRL 0x0104 +#define USB3P1_PIPE_RXD_MAP_MASK GENMASK(3, 0) +#define USB3P1_PIPE_RXD_MAP(n) (((n) & 0xf)) /* 0x0: PHY0 Lane0 or Lane2; 0x1: PHY1 Lane0 */ + +/* COMB1 PHY PIPE TXDATA MAP CTRL Register */ +#define COMB1_PHY_PIPE_TXDATA_MAP_CTRL 0x0c44 +#define COMB1_PHY_LN0_PIPE_TXD_MAP_MASK GENMASK(3, 0) +#define COMB1_PHY_LN0_PIPE_TXD_MAP(n) (((n) & 0xf)) /* 0x0: USB3.1 PIPE; 0x1: PCIE0 Lane0 */ + +/* SUBSYS COMB0 PHY Version Register */ +#define COMB0_PHY_VER 0x1FE0 +#define PHY0_GEN_VER GENMASK(31, 24) +#define PHY0_SUB_VER GENMASK(23, 16) +#define PHY0_PRJ_VER GENMASK(15, 8) + +/* SUBSYS COMB1 PHY Version Register */ +#define COMB1_PHY_VER 0x1FE4 +#define PHY1_GEN_VER GENMASK(31, 24) +#define PHY1_SUB_VER GENMASK(23, 16) +#define PHY1_PRJ_VER GENMASK(15, 8) + +/* SUBSYS TOP Version Register */ +#define SUBSYS_TOP_VER 0x1FFC +#define TOP_GEN_VER GENMASK(31, 24) +#define TOP_SUB_VER GENMASK(23, 16) +#define TOP_PRJ_VER GENMASK(15, 8) + +/* Registers */ +#define SUBSYS_REG_PCIEBGR(top_subsys_reg) ((top_subsys_reg) \ + + SUBSYS_PCIE_BGR) +#define SUBSYS_REG_USB3BGR(top_subsys_reg) ((top_subsys_reg) \ + + SUBSYS_USB3P1_BGR) +#define SUBSYS_REG_DBGCTRL(top_subsys_reg) ((top_subsys_reg) \ + + SUBSYS_DBG_CTL) + +#define COMB0_REG_PHYPINS_LINK_CTRL0(top_reg) ((top_reg) \ + + COMB0_PHY_PINS_LINK_CTRL0) + +#define COMB0_REG_PHYPINS_LINK_CTRL1(top_reg) ((top_reg) \ + + COMB0_PHY_PINS_LINK_CTRL1) + +#define COMB0_REG_PHYPINS_LINK_CTRL2(top_reg) ((top_reg) \ + + COMB0_PHY_PINS_LINK_CTRL2) + +#define COMB0_REG_PHYPINS_LINK_CTRL3(top_reg) ((top_reg) \ + + COMB0_PHY_PINS_LINK_CTRL3) + +#define COMB0_REG_PHYPINS_LINK_STUS0(top_reg) ((top_reg) \ + + COMB0_PHY_PINS_LINK_STUS0) + +#define COMB1_REG_PHYPINS_LINK_CTRL0(top_reg) ((top_reg) \ + + COMB1_PHY_PINS_LINK_CTRL0) + +#define COMB1_REG_PHYPINS_LINK_CTRL1(top_reg) ((top_reg) \ + + COMB1_PHY_PINS_LINK_CTRL1) + +#define COMB1_REG_PHYPINS_LINK_CTRL3(top_reg) ((top_reg) \ + + COMB1_PHY_PINS_LINK_CTRL3) + +#define COMB1_REG_PHYPINS_LANE0_CTRL0(top_reg) ((top_reg) \ + + COMB1_PHY_PINS_LANE0_CTRL0) + +#define COMB1_REG_PHYPINS_LINK_STUS0(top_reg) ((top_reg) \ + + COMB1_PHY_PINS_LINK_STUS0) + +#define DP_RAWS_TXDW_CONV_CRTL(top_combo_reg) ((top_combo_reg) \ + + DP_RAWS_TXDW_CONV_CTRL) + +#define USB3P1_REG_PIPE_CLK_MAP_CTRL(top_combo_reg) ((top_combo_reg) \ + + USB3P1_PIPE_CLK_MAP_CTRL) + +#define USB3P1_REG_PIPE_RXD_MAP_CTRL(top_combo_reg) ((top_combo_reg) \ + + USB3P1_PIPE_RXD_MAP_CTRL) + +#define COMB1_REG_PHYPIPE_TXDATA_MAP_CTRL(top_combo_reg) ((top_combo_reg) \ + + COMB1_PHY_PIPE_TXDATA_MAP_CTRL) + +#define COMB0_REG_PHY_VER(top_combo_reg) ((top_combo_reg) \ + + COMB0_PHY_VER) + +#define COMB1_REG_PHY_VER(top_combo_reg) ((top_combo_reg) \ + + COMB1_PHY_VER) + +#define SUBSYS_REG_TOP_VER(top_combo_reg) ((top_combo_reg) \ + + SUBSYS_TOP_VER) + +struct dp_signal_level { + u16 lane_txdrv[MAX_LANE_CNT]; + u16 lane_mgnfs[MAX_LANE_CNT]; + u16 lane_cpost[MAX_LANE_CNT]; +}; + +#define DP_TXDRV(r0, r1, r2, r3) \ + .lane_txdrv[0] = r0, \ + .lane_txdrv[1] = r1, \ + .lane_txdrv[2] = r2, \ + .lane_txdrv[3] = r3, + +#define DP_MGNFS(r0, r1, r2, r3) \ + .lane_mgnfs[0] = r0, \ + .lane_mgnfs[1] = r1, \ + .lane_mgnfs[2] = r2, \ + .lane_mgnfs[3] = r3, + +#define DP_CPOST(r0, r1, r2, r3) \ + .lane_cpost[0] = r0, \ + .lane_cpost[1] = r1, \ + .lane_cpost[2] = r2, \ + .lane_cpost[3] = r3, + + +static struct dp_signal_level dp_lv_table_5g4[MAX_SWING][MAX_PRE] = { + /* sw-0 */ + { + { + /* sw-0 pre-0 */ + DP_TXDRV(0x0003, 0x0003, 0x0003, 0x0003) + DP_MGNFS(0x0025, 0x0024, 0x0024, 0x0025) + DP_CPOST(0x0000, 0x0000, 0x0000, 0x0000) + }, + { + /* sw-0 pre-1 */ + DP_TXDRV(0x00A3, 0x00A3, 0x00A3, 0x00A3) + DP_MGNFS(0x001E, 0x001D, 0x001D, 0x001E) + DP_CPOST(0x000A, 0x000A, 0x000A, 0x000A) + }, + { + /* sw-0 pre-2 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x0011, 0x0010, 0x0010, 0x0011) + DP_CPOST(0x001C, 0x001C, 0x001C, 0x001C) + }, + { + /* sw-0 pre-3 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x0001, 0x0000, 0x0000, 0x0001) + DP_CPOST(0x0027, 0x0027, 0x0027, 0x0027) + }, + }, + /* sw-1 */ + { + { + /* sw-1 pre-0 */ + DP_TXDRV(0x0002, 0x0002, 0x0002, 0x0002) + DP_MGNFS(0x001D, 0x001C, 0x001C, 0x001D) + DP_CPOST(0x0000, 0x0000, 0x0000, 0x0000) + }, + { + /* sw-1 pre-1 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x0012, 0x0011, 0x0011, 0x0012) + DP_CPOST(0x000A, 0x000A, 0x000A, 0x000A) + }, + { + /* sw-1 pre-2 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x0001, 0x0000, 0x0000, 0x0001) + DP_CPOST(0x001E, 0x001E, 0x001E, 0x001E) + }, + { + /* sw-1 pre-3 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x0001, 0x0000, 0x0000, 0x0001) + DP_CPOST(0x001E, 0x001E, 0x001E, 0x001E) + }, + }, + /* sw-2 */ + { + { + /* sw-2 pre-0 */ + DP_TXDRV(0x0002, 0x0002, 0x0002, 0x0002) + DP_MGNFS(0x0010, 0x000F, 0x000F, 0x0010) + DP_CPOST(0x0000, 0x0000, 0x0000, 0x0000) + }, + { + /* sw-2 pre-1 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x0000, 0x0000, 0x0000, 0x0000) + DP_CPOST(0x000F, 0x000F, 0x000F, 0x000F) + }, + { + /* sw-2 pre-2 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x0000, 0x0000, 0x0000, 0x0000) + DP_CPOST(0x000F, 0x000F, 0x000F, 0x000F) + }, + { + /* sw-2 pre-3 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x0000, 0x0000, 0x0000, 0x0000) + DP_CPOST(0x000F, 0x000F, 0x000F, 0x000F) + }, + }, + /* sw-3 */ + { + { + /* sw-3 pre-0 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x0000, 0x0000, 0x0000, 0x0000) + DP_CPOST(0x0000, 0x0000, 0x0000, 0x0000) + }, + { + /* sw-3 pre-1 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x0000, 0x0000, 0x0000, 0x0000) + DP_CPOST(0x0000, 0x0000, 0x0000, 0x0000) + }, + { + /* sw-3 pre-2 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x0000, 0x0000, 0x0000, 0x0000) + DP_CPOST(0x0000, 0x0000, 0x0000, 0x0000) + }, + { + /* sw-3 pre-3 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x0000, 0x0000, 0x0000, 0x0000) + DP_CPOST(0x0000, 0x0000, 0x0000, 0x0000) + }, + }, + +}; + +static struct dp_signal_level dp_lv_table_2g7[MAX_SWING][MAX_PRE] = { + /* sw-0 */ + { + { + /* sw-0 pre-0 */ + DP_TXDRV(0x0003, 0x0003, 0x0003, 0x0003) + DP_MGNFS(0x0028, 0x0026, 0x0026, 0x0028) + DP_CPOST(0x0000, 0x0000, 0x0000, 0x0000) + }, + { + /* sw-0 pre-1 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x0024, 0x0023, 0x0023, 0x0024) + DP_CPOST(0x0008, 0x0008, 0x0008, 0x0008) + }, + { + /* sw-0 pre-2 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x001C, 0x001B, 0x001B, 0x001C) + DP_CPOST(0x0016, 0x0016, 0x0016, 0x0016) + }, + { + /* sw-0 pre-3 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x000B, 0x000A, 0x000A, 0x000B) + DP_CPOST(0x0023, 0x0023, 0x0023, 0x0023) + }, + }, + /* sw-1 */ + { + { + /* sw-1 pre-0 */ + DP_TXDRV(0x0003, 0x0003, 0x0003, 0x0003) + DP_MGNFS(0x001F, 0x001E, 0x001E, 0x001F) + DP_CPOST(0x0000, 0x0000, 0x0000, 0x0000) + }, + { + /* sw-1 pre-1 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x001A, 0x0019, 0x0019, 0x001A) + DP_CPOST(0x000A, 0x000A, 0x000A, 0x000A) + }, + { + /* sw-1 pre-2 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x000A, 0x0009, 0x0009, 0x000A) + DP_CPOST(0x0019, 0x0019, 0x0019, 0x0019) + }, + { + /* sw-1 pre-3 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x000A, 0x0009, 0x0009, 0x000A) + DP_CPOST(0x0019, 0x0019, 0x0019, 0x0019) + }, + }, + /* sw-2 */ + { + { + /* sw-2 pre-0 */ + DP_TXDRV(0x0003, 0x0003, 0x0003, 0x0003) + DP_MGNFS(0x0010, 0x000E, 0x000E, 0x0010) + DP_CPOST(0x0000, 0x0000, 0x0000, 0x0000) + }, + { + /* sw-2 pre-1 */ + DP_TXDRV(0x0082, 0x0082, 0x0082, 0x0082) + DP_MGNFS(0x0000, 0x0000, 0x0000, 0x0000) + DP_CPOST(0x0011, 0x0011, 0x0011, 0x0011) + }, + { + /* sw-2 pre-2 */ + DP_TXDRV(0x0082, 0x0082, 0x0082, 0x0082) + DP_MGNFS(0x0000, 0x0000, 0x0000, 0x0000) + DP_CPOST(0x0011, 0x0011, 0x0011, 0x0011) + }, + { + /* sw-2 pre-3 */ + DP_TXDRV(0x0082, 0x0082, 0x0082, 0x0082) + DP_MGNFS(0x0000, 0x0000, 0x0000, 0x0000) + DP_CPOST(0x0011, 0x0011, 0x0011, 0x0011) + }, + }, + /* sw-3 */ + { + { + /* sw-3 pre-0 */ + DP_TXDRV(0x0003, 0x0003, 0x0003, 0x0003) + DP_MGNFS(0x0000, 0x0000, 0x0000, 0x0000) + DP_CPOST(0x0000, 0x0000, 0x0000, 0x0000) + }, + { + /* sw-3 pre-1 */ + DP_TXDRV(0x0003, 0x0003, 0x0003, 0x0003) + DP_MGNFS(0x0000, 0x0000, 0x0000, 0x0000) + DP_CPOST(0x0000, 0x0000, 0x0000, 0x0000) + }, + { + /* sw-3 pre-2 */ + DP_TXDRV(0x0003, 0x0003, 0x0003, 0x0003) + DP_MGNFS(0x0000, 0x0000, 0x0000, 0x0000) + DP_CPOST(0x0000, 0x0000, 0x0000, 0x0000) + }, + { + /* sw-3 pre-3 */ + DP_TXDRV(0x0003, 0x0003, 0x0003, 0x0003) + DP_MGNFS(0x0000, 0x0000, 0x0000, 0x0000) + DP_CPOST(0x0000, 0x0000, 0x0000, 0x0000) + }, + }, + +}; + +static struct dp_signal_level dp_lv_table_1g62[MAX_SWING][MAX_PRE] = { + /* sw-0 */ + { + { + /* sw-0 pre-0 */ + DP_TXDRV(0x0003, 0x0003, 0x0003, 0x0003) + DP_MGNFS(0x0027, 0x0027, 0x0027, 0x0027) + DP_CPOST(0x0000, 0x0000, 0x0000, 0x0000) + }, + { + /* sw-0 pre-1 */ + DP_TXDRV(0x00A2, 0x00A2, 0x00A2, 0x00A2) + DP_MGNFS(0x0023, 0x0023, 0x0023, 0x0023) + DP_CPOST(0x000A, 0x000A, 0x000A, 0x000A) + }, + { + /* sw-0 pre-2 */ + DP_TXDRV(0x0092, 0x0092, 0x0092, 0x0092) + DP_MGNFS(0x001A, 0x001A, 0x001A, 0x001A) + DP_CPOST(0x0016, 0x0016, 0x0016, 0x0016) + }, + { + /* sw-0 pre-3 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x000E, 0x000E, 0x000E, 0x000E) + DP_CPOST(0x0020, 0x0020, 0x0020, 0x0020) + }, + }, + /* sw-1 */ + { + { + /* sw-1 pre-0 */ + DP_TXDRV(0x0003, 0x0003, 0x0003, 0x0003) + DP_MGNFS(0x0020, 0x0020, 0x0020, 0x0020) + DP_CPOST(0x0000, 0x0000, 0x0000, 0x0000) + }, + { + /* sw-1 pre-1 */ + DP_TXDRV(0x00A2, 0x00A2, 0x00A2, 0x00A2) + DP_MGNFS(0x0019, 0x0018, 0x0018, 0x0019) + DP_CPOST(0x000C, 0x000C, 0x000C, 0x000C) + }, + { + /* sw-1 pre-2 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x000D, 0x000C, 0x000C, 0x000D) + DP_CPOST(0x0017, 0x0017, 0x0017, 0x0017) + }, + { + /* sw-1 pre-3 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x000D, 0x000C, 0x000C, 0x000D) + DP_CPOST(0x0017, 0x0017, 0x0017, 0x0017) + }, + }, + /* sw-2 */ + { + { + /* sw-2 pre-0 */ + DP_TXDRV(0x0003, 0x0003, 0x0003, 0x0003) + DP_MGNFS(0x0010, 0x000F, 0x000F, 0x0010) + DP_CPOST(0x0000, 0x0000, 0x0000, 0x0000) + }, + { + /* sw-2 pre-1 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x0004, 0x0003, 0x0003, 0x0004) + DP_CPOST(0x0010, 0x0010, 0x0010, 0x0010) + }, + { + /* sw-2 pre-2 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x0004, 0x0003, 0x0003, 0x0004) + DP_CPOST(0x0010, 0x0010, 0x0010, 0x0010) + }, + { + /* sw-2 pre-3 */ + DP_TXDRV(0x00B2, 0x00B2, 0x00B2, 0x00B2) + DP_MGNFS(0x0004, 0x0003, 0x0003, 0x0004) + DP_CPOST(0x0010, 0x0010, 0x0010, 0x0010) + }, + }, + /* sw-3 */ + { + { + /* sw-3 pre-0 */ + DP_TXDRV(0x0003, 0x0003, 0x0003, 0x0003) + DP_MGNFS(0x0000, 0x0000, 0x0000, 0x0000) + DP_CPOST(0x0000, 0x0000, 0x0000, 0x0000) + }, + { + /* sw-3 pre-1 */ + DP_TXDRV(0x0003, 0x0003, 0x0003, 0x0003) + DP_MGNFS(0x0000, 0x0000, 0x0000, 0x0000) + DP_CPOST(0x0000, 0x0000, 0x0000, 0x0000) + }, + { + /* sw-3 pre-2 */ + DP_TXDRV(0x0003, 0x0003, 0x0003, 0x0003) + DP_MGNFS(0x0000, 0x0000, 0x0000, 0x0000) + DP_CPOST(0x0000, 0x0000, 0x0000, 0x0000) + }, + { + /* sw-3 pre-3 */ + DP_TXDRV(0x0003, 0x0003, 0x0003, 0x0003) + DP_MGNFS(0x0000, 0x0000, 0x0000, 0x0000) + DP_CPOST(0x0000, 0x0000, 0x0000, 0x0000) + }, + }, + +}; + +static void sunxi_cadence_phy_reset_control_put(void *data) +{ + reset_control_put(data); +} + +/* Serdes Sub-System Top Support. */ +static u32 top_ver_get(struct sunxi_cadence_phy *sunxi_cphy) +{ + u32 reg; + + reg = readl(SUBSYS_REG_TOP_VER(sunxi_cphy->top_combo_reg)); + + return reg; +} + +static void combo_usb2_clk_set(struct sunxi_cadence_phy *sunxi_cphy, bool enable) +{ + u32 val; + + val = readl(SUBSYS_REG_USB3BGR(sunxi_cphy->top_subsys_reg)); + if (enable) + val |= USB3P1_USB2P0_PHY_RSTN; + else + val &= ~USB3P1_USB2P0_PHY_RSTN; + writel(val, SUBSYS_REG_USB3BGR(sunxi_cphy->top_subsys_reg)); +} + +static void combo_usb_clk_set(struct sunxi_cadence_phy *sunxi_cphy, bool enable) +{ + u32 val, tmp = 0; + + val = readl(SUBSYS_REG_USB3BGR(sunxi_cphy->top_subsys_reg)); + tmp = USB3P1_ACLK_EN | USB3P1_HCLK_EN; + if (enable) + val |= tmp; + else + val &= ~tmp; + writel(val, SUBSYS_REG_USB3BGR(sunxi_cphy->top_subsys_reg)); +} + + +/*************************************************************************************************** + * Combo PHY0 Support. * + * - PIPE interface mode USB3.1 SS and SS+ * + * - Raw SerDes interface mode DisplayPort Tx / embedded DisplayPort Tx * + * - Supports up to 4Lane * + **************************************************************************************************/ +static void combo0_dp_phy_reset(struct sunxi_cadence_combophy *combo0, bool deassert) +{ + struct sunxi_cadence_phy *sunxi_cphy = combo0->sunxi_cphy; + u32 phy0_pma_cmn_ready = 0; + u32 reg_val; + u32 retry_cnt = 0; + + if (deassert) { + reg_val = readl(COMB0_REG_PHYPINS_LINK_CTRL2(combo0->top_reg)); + reg_val &= ~(PHY0_IDDQ_EN_SEL | PHY0_IDDQ_EN_SOFT); + writel(reg_val, COMB0_REG_PHYPINS_LINK_CTRL2(combo0->top_reg)); + + //PHY RESET + phy_writel(combo0->top_reg + 0x00, 0x0); + + //PHY RESET + phy_writel(combo0->top_reg + 0x00, 0x001); + msleep(1); + //DP LINK RESET + phy_writel(combo0->top_reg + 0x00, 0x101); + msleep(1); + //PHY RESET + phy_writel(combo0->top_reg + 0x00, 0x131); + msleep(1); + + reg_val = readl(COMB0_REG_PHYPINS_LINK_CTRL0(combo0->top_reg)); + if (combo0->orientation == TYPEC_ORIENTATION_REVERSE) + reg_val |= PHY0_TYPEC_CONN_DIR; + else + reg_val &= ~PHY0_TYPEC_CONN_DIR; + writel(reg_val, COMB0_REG_PHYPINS_LINK_CTRL0(combo0->top_reg)); + + msleep(1); + + while (phy0_pma_cmn_ready == 0) { + if (++retry_cnt > 1000) { + pr_warn("wrn: %s dp wait pll enable timeout line:%d\n", combo0->name, __LINE__); + break; + } + reg_val = phy_readl(combo0->top_reg+0x900); + reg_val = reg_val & (1 << 0); + phy0_pma_cmn_ready = reg_val; + + udelay(5); + } + + phy_set_lbits(sunxi_cphy->top_combo_reg + 0x4, 0, 1, 1); + phy_set_lbits(sunxi_cphy->top_combo_reg + 0x4, 4, 4, 0xf); + phy_set_lbits(sunxi_cphy->top_combo_reg + 0x4, 8, 4, 0xf); + + phy_set_lbits(combo0->top_reg + 0x0c, 0, 1, 1); + + phy0_pma_cmn_ready = 0; + retry_cnt = 0; + while (phy0_pma_cmn_ready == 0) { + if (++retry_cnt > 1000) { + pr_warn("wrn: %s dp wait pll enable timeout line:%d\n", combo0->name, __LINE__); + break; + } + reg_val = phy_readl(combo0->top_reg+0x900); + reg_val = (reg_val & (1 << 4)) >> 4; + phy0_pma_cmn_ready = reg_val; + + udelay(5); + } + + msleep(1); + + phy_set_lbits(combo0->top_reg + 0x0c, 4, 6, 1); + + phy0_pma_cmn_ready = 0; + retry_cnt = 0; + while (phy0_pma_cmn_ready == 0) { + if (++retry_cnt > 100000) { + pr_warn("wrn: %s dp wait pll enable timeout line:%d\n", combo0->name, __LINE__); + break; + } + reg_val = phy_readl(combo0->top_reg+0x900); + reg_val = (reg_val & (0x3f << 8)) >> 8; + phy0_pma_cmn_ready = reg_val; + + udelay(5); + } + } else { + //PHY RESET + phy_writel(combo0->top_reg + 0x00, 0x0); + phy_writel(sunxi_cphy->top_combo_reg + 0x4, 0x0); + phy_writel(combo0->top_reg + 0x0c, 0x0); + msleep(1); + } +} + +static int combo0_dp_set_rate(struct sunxi_cadence_combophy *combo0, + struct phy_configure_opts_dp *dp_config) +{ + u32 val; + + if (dp_config->set_rate == 1) { + /* if in typec STATE_D/F, skip phy configure/reset which has been set */ + if (combo0->usb_dp_state) { + switch (dp_config->link_rate) { + case 2700: + if (combo0->orientation == TYPEC_ORIENTATION_REVERSE) { + phy_writew(combo0->phy_reg + (0x01c1 << 1), 0x0701); + phy_writew(combo0->phy_reg + (0x40e7 << 1), 0x1); + phy_writew(combo0->phy_reg + (0x42e7 << 1), 0x1); + } else { + phy_writew(combo0->phy_reg + (0x01c1 << 1), 0x0701); + phy_writew(combo0->phy_reg + (0x44e7 << 1), 0x1); + phy_writew(combo0->phy_reg + (0x46e7 << 1), 0x1); + } + break; + case 5400: + if (combo0->orientation == TYPEC_ORIENTATION_REVERSE) { + phy_writew(combo0->phy_reg + (0x01c1 << 1), 0x0601); + phy_writew(combo0->phy_reg + (0x40e7 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x42e7 << 1), 0x0); + } else { + phy_writew(combo0->phy_reg + (0x01c1 << 1), 0x0601); + phy_writew(combo0->phy_reg + (0x44e7 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x46e7 << 1), 0x0); + } + break; + case 1620: + case 8100: + default: + pr_warn("[Cadence PHY]: Unsupport link_rate:%d state:%d\n", + dp_config->link_rate, combo0->usb_dp_state); + return -EINVAL; + } + combo0->link_rate = dp_config->link_rate; + } else { + combo0_dp_phy_reset(combo0, false); + switch (dp_config->link_rate) { + case 1620: + // set ref-clk to 26M + val = readl(COMB0_REG_PHYPINS_LINK_CTRL2(combo0->top_reg)); + val &= ~(PHY0_IDDQ_EN_SEL | PHY0_IDDQ_EN_SOFT); + writel(val, COMB0_REG_PHYPINS_LINK_CTRL2(combo0->top_reg)); + + phy_writel(combo0->top_reg + 0x4, 0x1100001); + + //CMN_SSM_BIAS_TMR + phy_writew(combo0->phy_reg + (0x0022 << 1), 0x001a); + //CMN_PLLSM0_PLLPRE_TMR + phy_writew(combo0->phy_reg + (0x002A << 1), 0x0034); + //CMN_PLLSM0_PLLLOCK_TMR + phy_writew(combo0->phy_reg + (0x002C << 1), 0x00da); + + //CMN_PLLSM1_PLLPRE_TMR + phy_writew(combo0->phy_reg + (0x0032 << 1), 0x0034); + //CMN_PLLSM1_PLLLOCK_TMR + phy_writew(combo0->phy_reg + (0x0034 << 1), 0x00da); + + //CMN_BGCAL_INIT_TMR + phy_writew(combo0->phy_reg + (0x0064 << 1), 0x0082); + //CMN_BGCAL_ITER_TMR + phy_writew(combo0->phy_reg + (0x0065 << 1), 0x0082); + //CMN_IBCAL_INIT_TMR + phy_writew(combo0->phy_reg + (0x0074 << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x0104 << 1), 0x0020); + phy_writew(combo0->phy_reg + (0x0105 << 1), 0x0007); + phy_writew(combo0->phy_reg + (0x010c << 1), 0x0020); + phy_writew(combo0->phy_reg + (0x010d << 1), 0x0007); + phy_writew(combo0->phy_reg + (0x0114 << 1), 0x030c); + phy_writew(combo0->phy_reg + (0x0115 << 1), 0x0007); + phy_writew(combo0->phy_reg + (0x0124 << 1), 0x0007); + phy_writew(combo0->phy_reg + (0x0125 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x0126 << 1), 0x000f); + phy_writew(combo0->phy_reg + (0x0128 << 1), 0x0132); + + phy_writew(combo0->phy_reg + (0x8044 << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x8244 << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x8444 << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x8644 << 1), 0x001a); + + phy_writew(combo0->phy_reg + (0x8045 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x8245 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x8445 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x8645 << 1), 0x0082); + + phy_writew(combo0->phy_reg + (0x804c << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x824c << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x844c << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x864c << 1), 0x001a); + + phy_writew(combo0->phy_reg + (0x804d << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x824d << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x844d << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x864d << 1), 0x0082); + + phy_writew(combo0->phy_reg + (0x4123 << 1), 0x0a28); + phy_writew(combo0->phy_reg + (0x4323 << 1), 0x0a28); + phy_writew(combo0->phy_reg + (0x4523 << 1), 0x0a28); + phy_writew(combo0->phy_reg + (0x4723 << 1), 0x0a28); + + //VCO calibration settings. + phy_writew(combo0->phy_reg + (0x00c4 << 1), 0x0104); + phy_writew(combo0->phy_reg + (0x00c5 << 1), 0x0005); + phy_writew(combo0->phy_reg + (0x00c6 << 1), 0x0337); + phy_writew(combo0->phy_reg + (0x00c8 << 1), 0x0335); + phy_writew(combo0->phy_reg + (0x00c2 << 1), 0x0003); + + phy_writew(combo0->phy_reg + (0x0084 << 1), 0x0104); + phy_writew(combo0->phy_reg + (0x0085 << 1), 0x0005); + phy_writew(combo0->phy_reg + (0x0086 << 1), 0x0337); + phy_writew(combo0->phy_reg + (0x0088 << 1), 0x0335); + phy_writew(combo0->phy_reg + (0x0082 << 1), 0x0003); + + //PLL lock detect settings + phy_writew(combo0->phy_reg + (0x00dc << 1), 0x00cf); + phy_writew(combo0->phy_reg + (0x00de << 1), 0x00ce); + phy_writew(combo0->phy_reg + (0x00dF << 1), 0x0005); + + phy_writew(combo0->phy_reg + (0x009c << 1), 0x00cf); + phy_writew(combo0->phy_reg + (0x009e << 1), 0x00ce); + phy_writew(combo0->phy_reg + (0x009F << 1), 0x0005); + + //DP_LANE_SET as follow + phy_writew(combo0->phy_reg + (0xc010 << 1), 0x51d9); + //phy DP lane enabled + phy_writew(combo0->phy_reg + (0xc011 << 1), 0x0100); + + //PHY_PMA_ISO_PLL_CTRL0 cmn_pll1_clk_en + phy_writew(combo0->phy_reg + (0xe003 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0xe005 << 1), 0x000B); + //PHY_PMA_ISO_PLL_CTRL1 + phy_writew(combo0->phy_reg + (0xe006 << 1), 0x2224); + + //TX_PSC_A0 + phy_writew(combo0->phy_reg + (0x4100 << 1), 0x00FB); + phy_writew(combo0->phy_reg + (0x4300 << 1), 0x00FB); + phy_writew(combo0->phy_reg + (0x4500 << 1), 0x00FB); + phy_writew(combo0->phy_reg + (0x4700 << 1), 0x00FB); + + //TX_PSC_A2 + phy_writew(combo0->phy_reg + (0x4102 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4302 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4502 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4702 << 1), 0x04AA); + + //TX_PSC_A3 + phy_writew(combo0->phy_reg + (0x4102 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4302 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4502 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4702 << 1), 0x04AA); + + //RX all set to 0 + //RX_PSC_A0 + phy_writew(combo0->phy_reg + (0x8000 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8200 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8400 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8600 << 1), 0x0); + + //RX_PSC_A0 + phy_writew(combo0->phy_reg + (0x8000 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8202 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8402 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8602 << 1), 0x0); + + //RX_PSC_A0 + phy_writew(combo0->phy_reg + (0x8002 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8203 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8403 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8603 << 1), 0x0); + + //RX_PSC_CAL + phy_writew(combo0->phy_reg + (0x8006 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8206 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8406 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8606 << 1), 0x0); + + //RX_REE_GCSM1_CTRL + phy_writew(combo0->phy_reg + (0x8108 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8308 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8508 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8708 << 1), 0x0); + + //RX_REE_GCSM2_CTRL + phy_writew(combo0->phy_reg + (0x8110 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8310 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8510 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8710 << 1), 0x0); + + //RX_REE_PERGCSM_CTRL + phy_writew(combo0->phy_reg + (0x8118 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8318 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8518 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8718 << 1), 0x0); + + //XCVR_DIAG_BIDI_CTRL diefferent mode with TX/RX enable + phy_writew(combo0->phy_reg + (0x40EA << 1), 0x000F); + phy_writew(combo0->phy_reg + (0x42EA << 1), 0x000F); + phy_writew(combo0->phy_reg + (0x44EA << 1), 0x000F); + phy_writew(combo0->phy_reg + (0x46EA << 1), 0x000F); + + //TX_TXCC_CTRL + phy_writew(combo0->phy_reg + (0x4040 << 1), 0x08A4); + phy_writew(combo0->phy_reg + (0x4240 << 1), 0x08A4); + phy_writew(combo0->phy_reg + (0x4440 << 1), 0x08A4); + phy_writew(combo0->phy_reg + (0x4640 << 1), 0x08A4); + + //DRV_DIAG_TX_DRV + phy_writew(combo0->phy_reg + (0x40C6 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x42C6 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x44C6 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x46C6 << 1), 0x0003); + + phy_writew(combo0->phy_reg + (0x4050 << 1), 0x002A); + phy_writew(combo0->phy_reg + (0x4250 << 1), 0x002A); + phy_writew(combo0->phy_reg + (0x4450 << 1), 0x002A); + phy_writew(combo0->phy_reg + (0x4650 << 1), 0x002A); + + phy_writew(combo0->phy_reg + (0x404C << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x424C << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x444C << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x464C << 1), 0x0000); + + //CMN-pull up-down res cal + phy_writew(combo0->phy_reg + (0x0103 << 1), 0x007F); + phy_writew(combo0->phy_reg + (0x010B << 1), 0x007F); + + phy_writew(combo0->phy_reg + (0x4140 << 1), 0x0801); + phy_writew(combo0->phy_reg + (0x4340 << 1), 0x0801); + phy_writew(combo0->phy_reg + (0x4540 << 1), 0x0801); + phy_writew(combo0->phy_reg + (0x4740 << 1), 0x0801); + + //ssc + if (combo0->ssc_en) + phy_writew(combo0->phy_reg + (0x00D8 << 1), 0x0001); + else + phy_writew(combo0->phy_reg + (0x00D8 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x00D9 << 1), 0x04C4); + phy_writew(combo0->phy_reg + (0x00DA << 1), 0x006A); + phy_writew(combo0->phy_reg + (0x00DB << 1), 0x0004); + + phy_writew(combo0->phy_reg + (0x0098 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x00D9 << 1), 0x044A); + phy_writew(combo0->phy_reg + (0x009A << 1), 0x006b); + phy_writew(combo0->phy_reg + (0x009B << 1), 0x0004); + + // pll configure + phy_writew(combo0->phy_reg + (0x00d4 << 1), 0x0004); + phy_writew(combo0->phy_reg + (0x01C4 << 1), 0x0509); + phy_writew(combo0->phy_reg + (0x01C5 << 1), 0x0F00); + phy_writew(combo0->phy_reg + (0x01C6 << 1), 0x0F08); + + phy_writew(combo0->phy_reg + (0x00d0 << 1), 0x0175); + phy_writew(combo0->phy_reg + (0x00d1 << 1), 0xD89E); + phy_writew(combo0->phy_reg + (0x00d2 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x00d3 << 1), 0x00FA); + phy_writew(combo0->phy_reg + (0x01c0 << 1), 0x0002); + + //SET common PLL1_CLOCK0 + phy_writew(combo0->phy_reg + (0x1c1 << 1), 0x0F01); + phy_writew(combo0->phy_reg + (0x004B << 1), 0x0); + + //dp XCVR(LANE) pll1 seting + phy_writew(combo0->phy_reg + (0x40e6 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x42e6 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x44e6 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x46e6 << 1), 0x0001); + + phy_writew(combo0->phy_reg + (0x40e7 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x42e7 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x44e7 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x46e7 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x40e5 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0x42e5 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0x44e5 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0x46e5 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0xe006 << 1), 0x2224); + + //PLL0 configure + phy_writew(combo0->phy_reg + (0x1A1 << 1), 0x8600); + phy_writew(combo0->phy_reg + (0x0094 << 1), 0x0004); + phy_writew(combo0->phy_reg + (0x01A4 << 1), 0x0509); + phy_writew(combo0->phy_reg + (0x01A5 << 1), 0x0F00); + phy_writew(combo0->phy_reg + (0x01A6 << 1), 0x0F08); + phy_writew(combo0->phy_reg + (0x0090 << 1), 0x0180); + phy_writew(combo0->phy_reg + (0x0091 << 1), 0x9DBA); + phy_writew(combo0->phy_reg + (0x0092 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x0093 << 1), 0x0102); + phy_writew(combo0->phy_reg + (0x01A0 << 1), 0x0002); + break; + case 2160: + break; + case 2430: + break; + case 2700: + val = readl(COMB0_REG_PHYPINS_LINK_CTRL2(combo0->top_reg)); + val &= ~(PHY0_IDDQ_EN_SEL | PHY0_IDDQ_EN_SOFT); + writel(val, COMB0_REG_PHYPINS_LINK_CTRL2(combo0->top_reg)); + + phy_writel(combo0->top_reg + 0x4, 0x1100001); + + //CMN_SSM_BIAS_TMR + phy_writew(combo0->phy_reg + (0x0022 << 1), 0x001a); + + //CMN_PLLSM0_PLLPRE_TMR + phy_writew(combo0->phy_reg + (0x002A << 1), 0x0034); + //CMN_PLLSM0_PLLLOCK_TMR + phy_writew(combo0->phy_reg + (0x002C << 1), 0x00da); + + //CMN_PLLSM1_PLLPRE_TMR + phy_writew(combo0->phy_reg + (0x0032 << 1), 0x0034); + //CMN_PLLSM1_PLLLOCK_TMR + phy_writew(combo0->phy_reg + (0x0034 << 1), 0x00da); + + //CMN_BGCAL_INIT_TMR + phy_writew(combo0->phy_reg + (0x0064 << 1), 0x0082); + //CMN_BGCAL_ITER_TMR + phy_writew(combo0->phy_reg + (0x0065 << 1), 0x0082); + //CMN_IBCAL_INIT_TMR + phy_writew(combo0->phy_reg + (0x0074 << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x0104 << 1), 0x0020); + phy_writew(combo0->phy_reg + (0x0105 << 1), 0x0007); + phy_writew(combo0->phy_reg + (0x010c << 1), 0x0020); + phy_writew(combo0->phy_reg + (0x010d << 1), 0x0007); + phy_writew(combo0->phy_reg + (0x0114 << 1), 0x030c); + phy_writew(combo0->phy_reg + (0x0115 << 1), 0x0007); + phy_writew(combo0->phy_reg + (0x0124 << 1), 0x0007); + phy_writew(combo0->phy_reg + (0x0125 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x0126 << 1), 0x000f); + phy_writew(combo0->phy_reg + (0x0128 << 1), 0x0132); + + phy_writew(combo0->phy_reg + (0x8044 << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x8244 << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x8444 << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x8644 << 1), 0x001a); + + phy_writew(combo0->phy_reg + (0x8045 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x8245 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x8445 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x8645 << 1), 0x0082); + + phy_writew(combo0->phy_reg + (0x804c << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x824c << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x844c << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x864c << 1), 0x001a); + + phy_writew(combo0->phy_reg + (0x804d << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x824d << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x844d << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x864d << 1), 0x0082); + + phy_writew(combo0->phy_reg + (0x4123 << 1), 0x0a28); + phy_writew(combo0->phy_reg + (0x4323 << 1), 0x0a28); + phy_writew(combo0->phy_reg + (0x4523 << 1), 0x0a28); + phy_writew(combo0->phy_reg + (0x4723 << 1), 0x0a28); + + //VCO calibration settings. + phy_writew(combo0->phy_reg + (0x00c4 << 1), 0x0104); + phy_writew(combo0->phy_reg + (0x00c5 << 1), 0x0005); + phy_writew(combo0->phy_reg + (0x00c6 << 1), 0x0337); + phy_writew(combo0->phy_reg + (0x00c8 << 1), 0x0335); + phy_writew(combo0->phy_reg + (0x00c2 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x0084 << 1), 0x0104); + phy_writew(combo0->phy_reg + (0x0085 << 1), 0x0005); + phy_writew(combo0->phy_reg + (0x0086 << 1), 0x0337); + phy_writew(combo0->phy_reg + (0x0088 << 1), 0x0335); + phy_writew(combo0->phy_reg + (0x0082 << 1), 0x0003); + + //PLL lock detect settings + phy_writew(combo0->phy_reg + (0x00dc << 1), 0x00cf); + phy_writew(combo0->phy_reg + (0x00de << 1), 0x00ce); + phy_writew(combo0->phy_reg + (0x00dF << 1), 0x0005); + phy_writew(combo0->phy_reg + (0x009c << 1), 0x00cf); + phy_writew(combo0->phy_reg + (0x009e << 1), 0x00ce); + phy_writew(combo0->phy_reg + (0x009F << 1), 0x0005); + + //DP_LANE_SET + phy_writew(combo0->phy_reg + (0xc010 << 1), 0x51d9); + phy_writew(combo0->phy_reg + (0xc011 << 1), 0x0100); + //PHY_PMA_ISO_PLL_CTRL0 cmn_pll1_clk_en + phy_writew(combo0->phy_reg + (0xe003 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0xe005 << 1), 0x000B); + //PHY_PMA_ISO_PLL_CTRL1 + phy_writew(combo0->phy_reg + (0xe006 << 1), 0x2224); + //TX_PSC_A0 + phy_writew(combo0->phy_reg + (0x4100 << 1), 0x00FB); + phy_writew(combo0->phy_reg + (0x4300 << 1), 0x00FB); + phy_writew(combo0->phy_reg + (0x4500 << 1), 0x00FB); + phy_writew(combo0->phy_reg + (0x4700 << 1), 0x00FB); + + //TX_PSC_A2 + phy_writew(combo0->phy_reg + (0x4102 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4302 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4502 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4702 << 1), 0x04AA); + + //TX_PSC_A3 + phy_writew(combo0->phy_reg + (0x4102 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4302 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4502 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4702 << 1), 0x04AA); + + //RX all set to 0 + phy_writew(combo0->phy_reg + (0x8000 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8200 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8400 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8600 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8000 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8202 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8402 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8602 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8002 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8203 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8403 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8603 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8006 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8206 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8406 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8606 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8108 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8308 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8508 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8708 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8110 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8310 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8510 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8710 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8118 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8318 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8518 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8718 << 1), 0x0); + + //XCVR_DIAG_BIDI_CTRL + phy_writew(combo0->phy_reg + (0x40EA << 1), 0x000F); + phy_writew(combo0->phy_reg + (0x42EA << 1), 0x000F); + phy_writew(combo0->phy_reg + (0x44EA << 1), 0x000F); + phy_writew(combo0->phy_reg + (0x46EA << 1), 0x000F); + + //TX_TXCC_CTRL + phy_writew(combo0->phy_reg + (0x4040 << 1), 0x08A4); + phy_writew(combo0->phy_reg + (0x4240 << 1), 0x08A4); + phy_writew(combo0->phy_reg + (0x4440 << 1), 0x08A4); + phy_writew(combo0->phy_reg + (0x4640 << 1), 0x08A4); + + //DRV_DIAG_TX_DRV + phy_writew(combo0->phy_reg + (0x40C6 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x42C6 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x44C6 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x46C6 << 1), 0x0003); + + phy_writew(combo0->phy_reg + (0x4050 << 1), 0x002A); + phy_writew(combo0->phy_reg + (0x4250 << 1), 0x002A); + phy_writew(combo0->phy_reg + (0x4450 << 1), 0x002A); + phy_writew(combo0->phy_reg + (0x4650 << 1), 0x002A); + + phy_writew(combo0->phy_reg + (0x404C << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x424C << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x444C << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x464C << 1), 0x0000); + + //CMN-pull up-down res cal + phy_writew(combo0->phy_reg + (0x0103 << 1), 0x007F); + phy_writew(combo0->phy_reg + (0x010B << 1), 0x007F); + + //ssc + if (combo0->ssc_en) + phy_writew(combo0->phy_reg + (0x00D8 << 1), 0x0001); + else + phy_writew(combo0->phy_reg + (0x00D8 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x00D9 << 1), 0x04C4); + phy_writew(combo0->phy_reg + (0x00DA << 1), 0x006A); + phy_writew(combo0->phy_reg + (0x00DB << 1), 0x0004); + + phy_writew(combo0->phy_reg + (0x0098 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x00D9 << 1), 0x045f); + phy_writew(combo0->phy_reg + (0x009A << 1), 0x006b); + phy_writew(combo0->phy_reg + (0x009B << 1), 0x0004); + + // pll configure + phy_writew(combo0->phy_reg + (0x00d4 << 1), 0x0004); + phy_writew(combo0->phy_reg + (0x01C4 << 1), 0x0509); + phy_writew(combo0->phy_reg + (0x01C5 << 1), 0x0F00); + phy_writew(combo0->phy_reg + (0x01C6 << 1), 0x0F08); + + phy_writew(combo0->phy_reg + (0x00d0 << 1), 0x019f); + phy_writew(combo0->phy_reg + (0x00d1 << 1), 0x6276); + phy_writew(combo0->phy_reg + (0x00d2 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x00d3 << 1), 0x0116); + phy_writew(combo0->phy_reg + (0x01c0 << 1), 0x0002); + + phy_writew(combo0->phy_reg + (0x1c1 << 1), 0x0701); + phy_writew(combo0->phy_reg + (0x004B << 1), 0x0); + + //dp XCVR(LANE) pll1 seting + phy_writew(combo0->phy_reg + (0x40e6 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x42e6 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x44e6 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x46e6 << 1), 0x0001); + + phy_writew(combo0->phy_reg + (0x40e7 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x42e7 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x44e7 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x46e7 << 1), 0x0001); + + phy_writew(combo0->phy_reg + (0x40e5 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0x42e5 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0x44e5 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0x46e5 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0xe006 << 1), 0x2224); + + // pll0 configure + phy_writew(combo0->phy_reg + (0x1A1 << 1), 0x8600); + phy_writew(combo0->phy_reg + (0x0094 << 1), 0x0004); + phy_writew(combo0->phy_reg + (0x01A4 << 1), 0x0509); + phy_writew(combo0->phy_reg + (0x01A5 << 1), 0x0F00); + phy_writew(combo0->phy_reg + (0x01A6 << 1), 0x0F08); + phy_writew(combo0->phy_reg + (0x0090 << 1), 0x0180); + phy_writew(combo0->phy_reg + (0x0091 << 1), 0x9DBA); + phy_writew(combo0->phy_reg + (0x0092 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x0093 << 1), 0x0102); + phy_writew(combo0->phy_reg + (0x01A0 << 1), 0x0002); + break; + case 3240: + break; + case 4320: + break; + case 5400: + val = readl(COMB0_REG_PHYPINS_LINK_CTRL2(combo0->top_reg)); + val &= ~(PHY0_IDDQ_EN_SEL | PHY0_IDDQ_EN_SOFT); + writel(val, COMB0_REG_PHYPINS_LINK_CTRL2(combo0->top_reg)); + + phy_writel(combo0->top_reg + 0x4, 0x1100001); + + //CMN_SSM_BIAS_TMR + phy_writew(combo0->phy_reg + (0x0022 << 1), 0x001a); + //CMN_PLLSM0_PLLPRE_TMR + phy_writew(combo0->phy_reg + (0x002A << 1), 0x0034); + //CMN_PLLSM0_PLLLOCK_TMR + phy_writew(combo0->phy_reg + (0x002C << 1), 0x00da); + //CMN_PLLSM1_PLLPRE_TMR + phy_writew(combo0->phy_reg + (0x0032 << 1), 0x0034); + //CMN_PLLSM1_PLLLOCK_TMR + phy_writew(combo0->phy_reg + (0x0034 << 1), 0x00da); + //CMN_BGCAL_INIT_TMR + phy_writew(combo0->phy_reg + (0x0064 << 1), 0x0082); + //CMN_BGCAL_ITER_TMR + phy_writew(combo0->phy_reg + (0x0065 << 1), 0x0082); + //CMN_IBCAL_INIT_TMR + phy_writew(combo0->phy_reg + (0x0074 << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x0104 << 1), 0x0020); + phy_writew(combo0->phy_reg + (0x0105 << 1), 0x0007); + phy_writew(combo0->phy_reg + (0x010c << 1), 0x0020); + phy_writew(combo0->phy_reg + (0x010d << 1), 0x0007); + phy_writew(combo0->phy_reg + (0x0114 << 1), 0x030c); + phy_writew(combo0->phy_reg + (0x0115 << 1), 0x0007); + phy_writew(combo0->phy_reg + (0x0124 << 1), 0x0007); + phy_writew(combo0->phy_reg + (0x0125 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x0126 << 1), 0x000f); + phy_writew(combo0->phy_reg + (0x0128 << 1), 0x0132); + phy_writew(combo0->phy_reg + (0x8044 << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x8244 << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x8444 << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x8644 << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x8045 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x8245 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x8445 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x8645 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x804c << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x824c << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x844c << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x864c << 1), 0x001a); + phy_writew(combo0->phy_reg + (0x804d << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x824d << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x844d << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x864d << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x4123 << 1), 0x0a28); + phy_writew(combo0->phy_reg + (0x4323 << 1), 0x0a28); + phy_writew(combo0->phy_reg + (0x4523 << 1), 0x0a28); + phy_writew(combo0->phy_reg + (0x4723 << 1), 0x0a28); + + //VCO calibration settings. + phy_writew(combo0->phy_reg + (0x00c4 << 1), 0x0104); + phy_writew(combo0->phy_reg + (0x00c5 << 1), 0x0005); + phy_writew(combo0->phy_reg + (0x00c6 << 1), 0x0337); + phy_writew(combo0->phy_reg + (0x00c8 << 1), 0x0335); + phy_writew(combo0->phy_reg + (0x00c2 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x0084 << 1), 0x0104); + phy_writew(combo0->phy_reg + (0x0085 << 1), 0x0005); + phy_writew(combo0->phy_reg + (0x0086 << 1), 0x0337); + phy_writew(combo0->phy_reg + (0x0088 << 1), 0x0335); + phy_writew(combo0->phy_reg + (0x0082 << 1), 0x0003); + + //PLL lock detect settings + phy_writew(combo0->phy_reg + (0x00dc << 1), 0x00cf); + phy_writew(combo0->phy_reg + (0x00de << 1), 0x00ce); + phy_writew(combo0->phy_reg + (0x00dF << 1), 0x0005); + phy_writew(combo0->phy_reg + (0x009c << 1), 0x00cf); + phy_writew(combo0->phy_reg + (0x009e << 1), 0x00ce); + phy_writew(combo0->phy_reg + (0x009F << 1), 0x0005); + + //DP_LANE_SET + //lane mapping PHY_PMA_LANE_MAP + phy_writew(combo0->phy_reg + (0xc010 << 1), 0x51d9); + //phy DP lane enabled + phy_writew(combo0->phy_reg + (0xc011 << 1), 0x0100); + //PHY_PMA_ISO_PLL_CTRL0 cmn_pll1_clk_en + phy_writew(combo0->phy_reg + (0xe003 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0xe005 << 1), 0x000B); + //PHY_PMA_ISO_PLL_CTRL1 + phy_writew(combo0->phy_reg + (0xe006 << 1), 0x2224); + //TX_PSC_A0 + phy_writew(combo0->phy_reg + (0x4100 << 1), 0x00FB); + phy_writew(combo0->phy_reg + (0x4300 << 1), 0x00FB); + phy_writew(combo0->phy_reg + (0x4500 << 1), 0x00FB); + phy_writew(combo0->phy_reg + (0x4700 << 1), 0x00FB); + phy_writew(combo0->phy_reg + (0x4102 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4302 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4502 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4702 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4102 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4302 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4502 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4702 << 1), 0x04AA); + + //RX all set to 0 + phy_writew(combo0->phy_reg + (0x8000 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8200 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8400 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8600 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8000 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8202 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8402 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8602 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8002 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8203 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8403 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8603 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8006 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8206 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8406 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8606 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8108 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8308 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8508 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8708 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8110 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8310 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8510 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8710 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8118 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8318 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8518 << 1), 0x0); + phy_writew(combo0->phy_reg + (0x8718 << 1), 0x0); + + //XCVR_DIAG_BIDI_CTRL + phy_writew(combo0->phy_reg + (0x40EA << 1), 0x000F); + phy_writew(combo0->phy_reg + (0x42EA << 1), 0x000F); + phy_writew(combo0->phy_reg + (0x44EA << 1), 0x000F); + phy_writew(combo0->phy_reg + (0x46EA << 1), 0x000F); + + //TX_TXCC_CTRL + phy_writew(combo0->phy_reg + (0x4040 << 1), 0x08A4); + phy_writew(combo0->phy_reg + (0x4240 << 1), 0x08A4); + phy_writew(combo0->phy_reg + (0x4440 << 1), 0x08A4); + phy_writew(combo0->phy_reg + (0x4640 << 1), 0x08A4); + + //DRV_DIAG_TX_DRV + phy_writew(combo0->phy_reg + (0x40C6 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x42C6 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x44C6 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x46C6 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x4050 << 1), 0x002A); + phy_writew(combo0->phy_reg + (0x4250 << 1), 0x002A); + phy_writew(combo0->phy_reg + (0x4450 << 1), 0x002A); + phy_writew(combo0->phy_reg + (0x4650 << 1), 0x002A); + phy_writew(combo0->phy_reg + (0x404C << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x424C << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x444C << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x464C << 1), 0x0000); + + //CMN-pull-up-down res cal + phy_writew(combo0->phy_reg + (0x0103 << 1), 0x007F); + phy_writew(combo0->phy_reg + (0x010B << 1), 0x007F); + + //ssc + if (combo0->ssc_en) + phy_writew(combo0->phy_reg + (0x00D8 << 1), 0x0001); + else + phy_writew(combo0->phy_reg + (0x00D8 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x00D9 << 1), 0x04C4); + phy_writew(combo0->phy_reg + (0x00DA << 1), 0x006A); + phy_writew(combo0->phy_reg + (0x00DB << 1), 0x0004); + + phy_writew(combo0->phy_reg + (0x0098 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x00D9 << 1), 0x045f); + phy_writew(combo0->phy_reg + (0x009A << 1), 0x006b); + phy_writew(combo0->phy_reg + (0x009B << 1), 0x0004); + + // pll configure + phy_writew(combo0->phy_reg + (0x00d4 << 1), 0x0004); + phy_writew(combo0->phy_reg + (0x01C4 << 1), 0x0509); + phy_writew(combo0->phy_reg + (0x01C5 << 1), 0x0F00); + phy_writew(combo0->phy_reg + (0x01C6 << 1), 0x0F08); + + phy_writew(combo0->phy_reg + (0x00d0 << 1), 0x019f); + phy_writew(combo0->phy_reg + (0x00d1 << 1), 0x6276); + phy_writew(combo0->phy_reg + (0x00d2 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x00d3 << 1), 0x0116); + phy_writew(combo0->phy_reg + (0x01c0 << 1), 0x0002); + + //SET common PLL1_CLOCK0 + phy_writew(combo0->phy_reg + (0x1c1 << 1), 0x0601); + phy_writew(combo0->phy_reg + (0x004B << 1), 0x0); + + //dp XCVR(LANE) pll1 seting + phy_writew(combo0->phy_reg + (0x40e6 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x42e6 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x44e6 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x46e6 << 1), 0x0001); + + phy_writew(combo0->phy_reg + (0x40e7 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x42e7 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x44e7 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x46e7 << 1), 0x0000); + + phy_writew(combo0->phy_reg + (0x40e5 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0x42e5 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0x44e5 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0x46e5 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0xe006 << 1), 0x2224); + + phy_writew(combo0->phy_reg + (0x1A1 << 1), 0x8600); + phy_writew(combo0->phy_reg + (0x0094 << 1), 0x0004); + phy_writew(combo0->phy_reg + (0x01A4 << 1), 0x0509); + phy_writew(combo0->phy_reg + (0x01A5 << 1), 0x0F00); + phy_writew(combo0->phy_reg + (0x01A6 << 1), 0x0F08); + phy_writew(combo0->phy_reg + (0x0090 << 1), 0x0180); + phy_writew(combo0->phy_reg + (0x0091 << 1), 0x9DBA); + phy_writew(combo0->phy_reg + (0x0092 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x0093 << 1), 0x0102); + phy_writew(combo0->phy_reg + (0x01A0 << 1), 0x0002); + break; + case 8100: + break; + default: + pr_err("[sunxi cadence phy]: not support rate(%d Mb/s) for dp\n", dp_config->link_rate); + return -1; + } + + combo0_dp_phy_reset(combo0, true); + combo0->link_rate = dp_config->link_rate; + } + + } + + return 0; +} + +static int combo0_dp_set_lanes(struct sunxi_cadence_combophy *combo0, + struct phy_configure_opts_dp *dp_config) +{ + + if (dp_config->set_lanes == 1) { + /* usb plugout would clear congiguration, we should re-config it */ + if (!COMBO_PHY_TYPE(combo0->mode, PHY_TYPE_USB3)) { + combo0_dp_phy_reset(combo0, false); + combo0_configure_usb_dp(combo0); + combo0_dp_phy_reset(combo0, true); + } + + /* usb_dp_state need enable all lanes */ + if (combo0->usb_dp_state) { + phy_writew(combo0->phy_reg + (0x4011 << 1), 0x0100); + } else { + switch (dp_config->lanes) { + case 1: + // close lane 321 + phy_writew(combo0->phy_reg + (0x4011 << 1), 0x010E); + break; + case 2: + // close lane 32 + phy_writew(combo0->phy_reg + (0x4011 << 1), 0x010C); + break; + case 4: + phy_writew(combo0->phy_reg + (0x4011 << 1), 0x0100); + break; + default: + pr_err("[sunxi cadence phy]: not support lanes(%d) for dp\n", dp_config->lanes); + return -1;; + } + } + } + + return 0; +} + +static int combo0_dp_set_sw_pre(struct sunxi_cadence_combophy *combo0, + struct phy_configure_opts_dp *dp_config) +{ + int i = 0; + u16 lane_txdrv = 0; + u16 lane_mgnfs = 0; + u16 lane_cpost = 0; + unsigned int sw = 0; + unsigned int pre = 0; + u32 offset; + + if (dp_config->set_voltages == 1) { + /* configure lane before sw-pre */ + for (i = 0; i < dp_config->lanes; i++) { + if (combo0->usb_dp_state) { + if (combo0->orientation == TYPEC_ORIENTATION_REVERSE) + offset = combo0->typec_remap[MAX_LANE_NO - i] * 0x200; + else + offset = combo0->typec_remap[i] * 0x200; + phy_writew(combo0->phy_reg + ((0x41E7 + offset) << 1), 0x0001); + } else { + offset = i * 0x200; + phy_writew(combo0->phy_reg + ((0x41E7 + offset) << 1), 0x0001); + } + } + + udelay(10); + + for (i = 0; i < dp_config->lanes; i++) { + if (combo0->usb_dp_state) { + if (combo0->orientation == TYPEC_ORIENTATION_REVERSE) + offset = combo0->typec_remap[MAX_LANE_NO - i] * 0x200; + else + offset = combo0->typec_remap[i] * 0x200; + phy_writew(combo0->phy_reg + ((0x4040 + offset) << 1), 0x08A4); + } else { + offset = i * 0x200; + phy_writew(combo0->phy_reg + ((0x4040 + offset) << 1), 0x08A4); + } + } + // end of configure + + for (i = 0; i < dp_config->lanes; i++) { + sw = dp_config->voltage[i]; + pre = dp_config->pre[i]; + + switch (combo0->link_rate) { + case 5400: + lane_txdrv = dp_lv_table_5g4[sw][pre].lane_txdrv[i]; + lane_mgnfs = dp_lv_table_5g4[sw][pre].lane_mgnfs[i]; + lane_cpost = dp_lv_table_5g4[sw][pre].lane_cpost[i]; + break; + case 2700: + lane_txdrv = dp_lv_table_2g7[sw][pre].lane_txdrv[i]; + lane_mgnfs = dp_lv_table_2g7[sw][pre].lane_mgnfs[i]; + lane_cpost = dp_lv_table_2g7[sw][pre].lane_cpost[i]; + break; + case 1620: + if (combo0->usb_dp_state) { + pr_err("[sunxi cadence phy]: not support rate(%d Mb/s) for dp signal level\n", + dp_config->link_rate); + return -1; + } + lane_txdrv = dp_lv_table_1g62[sw][pre].lane_txdrv[i]; + lane_mgnfs = dp_lv_table_1g62[sw][pre].lane_mgnfs[i]; + lane_cpost = dp_lv_table_1g62[sw][pre].lane_cpost[i]; + break; + default: + pr_err("[sunxi cadence phy]: not support rate(%d Mb/s) for dp signal level\n", + dp_config->link_rate); + return -1; + } + + if (combo0->usb_dp_state) { + /* FIXME: + * this usp_dp param only works if dp->typec connection use + * standard hardware design in 733 evb/P50AI */ + if (combo0->orientation == TYPEC_ORIENTATION_REVERSE) { + offset = combo0->typec_remap[MAX_LANE_NO - i] * 0x400; + phy_writew(combo0->phy_reg + (0x818C + offset), lane_txdrv); + phy_writew(combo0->phy_reg + (0x80A0 + offset), lane_mgnfs); + phy_writew(combo0->phy_reg + (0x8098 + offset), lane_cpost); + } else { + offset = combo0->typec_remap[i] * 0x400; + phy_writew(combo0->phy_reg + (0x818C + offset), lane_txdrv); + phy_writew(combo0->phy_reg + (0x80A0 + offset), lane_mgnfs); + phy_writew(combo0->phy_reg + (0x8098 + offset), lane_cpost); + } + } else { + phy_writew(combo0->phy_reg + (0x818C + (i * 0x400)), lane_txdrv); + phy_writew(combo0->phy_reg + (0x80A0 + (i * 0x400)), lane_mgnfs); + phy_writew(combo0->phy_reg + (0x8098 + (i * 0x400)), lane_cpost); + } + + } + + /* configure after sw-pre */ + udelay(10); + + for (i = 0; i < dp_config->lanes; i++) { + if (combo0->usb_dp_state) { + if (combo0->orientation == TYPEC_ORIENTATION_REVERSE) + offset = combo0->typec_remap[MAX_LANE_NO - i] * 0x200; + else + offset = combo0->typec_remap[i] * 0x200; + phy_writew(combo0->phy_reg + ((0x41E7 + offset) << 1), 0x0000); + } else { + offset = i * 0x200; + phy_writew(combo0->phy_reg + ((0x41E7 + offset) << 1), 0x0000); + } + } + } + + return 0; +} + +static int combo0_dp_set_ssc(struct sunxi_cadence_combophy *combo0, + struct phy_configure_opts_dp *dp_config) +{ + /* enable ssc if need */ + if (dp_config->ssc == 1) + combo0->ssc_en = 1; + + return 0; +} + +static void combo0_dp_phy_init(struct sunxi_cadence_combophy *combo0) +{ + struct sunxi_cadence_phy *sunxi_cphy = combo0->sunxi_cphy; + int ret; + + if (sunxi_cphy->avdd_h_regulator) + ret = regulator_enable(sunxi_cphy->avdd_h_regulator); + + if (sunxi_cphy->avdd_d_regulator) + ret = regulator_enable(sunxi_cphy->avdd_d_regulator); + + if (sunxi_cphy->avdd_c_regulator) + ret = regulator_enable(sunxi_cphy->avdd_c_regulator); + + if (sunxi_cphy->avdd_aux_regulator) + ret = regulator_enable(sunxi_cphy->avdd_aux_regulator); + + if (sunxi_cphy->dcxo_serdes0_clk) + clk_prepare_enable(sunxi_cphy->dcxo_serdes0_clk); + + if (sunxi_cphy->serdes_clk) + clk_prepare_enable(sunxi_cphy->serdes_clk); + + if (sunxi_cphy->serdes_reset) + reset_control_deassert(sunxi_cphy->serdes_reset); + + if (combo0->clk) + clk_prepare_enable(combo0->clk); +} + +static void combo0_dp_phy_exit(struct sunxi_cadence_combophy *combo0) +{ + struct sunxi_cadence_phy *sunxi_cphy = combo0->sunxi_cphy; + + if (combo0->clk) + clk_disable_unprepare(combo0->clk); + + if (sunxi_cphy->serdes_reset) + reset_control_assert(sunxi_cphy->serdes_reset); + + if (sunxi_cphy->serdes_clk) + clk_disable_unprepare(sunxi_cphy->serdes_clk); + + if (sunxi_cphy->dcxo_serdes0_clk) + clk_disable_unprepare(sunxi_cphy->dcxo_serdes0_clk); + + if (sunxi_cphy->avdd_h_regulator) + regulator_disable(sunxi_cphy->avdd_h_regulator); + + if (sunxi_cphy->avdd_d_regulator) + regulator_disable(sunxi_cphy->avdd_d_regulator); + + if (sunxi_cphy->avdd_c_regulator) + regulator_disable(sunxi_cphy->avdd_c_regulator); + + if (sunxi_cphy->avdd_aux_regulator) + regulator_disable(sunxi_cphy->avdd_aux_regulator); + +} + +static void combo0_dp_lane_remap(struct sunxi_cadence_combophy *combo0, + enum typec_orientation orientation) +{ + struct sunxi_cadence_phy *sunxi_cphy = combo0->sunxi_cphy; + int val = 0; + + //set phy's lane remap + if (orientation != TYPEC_ORIENTATION_REVERSE) { + val |= combo0->lane_remap[0] << 0; + val |= combo0->lane_remap[1] << 4; + val |= combo0->lane_remap[2] << 8; + val |= combo0->lane_remap[3] << 12; + } else { + val |= combo0->lane_remap[3] << 0; + val |= combo0->lane_remap[2] << 4; + val |= combo0->lane_remap[1] << 8; + val |= combo0->lane_remap[0] << 12; + } + phy_writel(sunxi_cphy->top_combo_reg + 0xc24, val); +} + +static void combo0_dp_lane_invert(struct sunxi_cadence_combophy *combo0) +{ + int val = 0, i = 0; + + for (i = 0; i < MAX_LANE_CNT; i++) { + val = phy_readw(combo0->phy_reg + ((0xF000 + (i * 0x100)) << 1)); + val &= ~(1 << 8); + val |= combo0->lane_invert[i] ? (1 << 8) : (0 << 8); + phy_writew(combo0->phy_reg + ((0xF000 + (i * 0x100)) << 1), val); + } +} + +static void combo0_dp_phy_power_on(struct sunxi_cadence_combophy *combo0) +{ + // set lane remap + combo0_dp_lane_remap(combo0, TYPEC_ORIENTATION_NONE); + + // set lane invert + combo0_dp_lane_invert(combo0); +} + +static void combo0_dp_phy_power_off(struct sunxi_cadence_combophy *combo0) +{ +} + +static int combo0_configure_usb_dp(struct sunxi_cadence_combophy *combo0) +{ + u32 val = 0; + + pr_debug("==================== orientation: %s USB configured: %s ====================", + typec_orientation_name[combo0->orientation], COMBO_PHY_TYPE(combo0->mode, PHY_TYPE_USB3) ? "yes" : "no"); + + val = readl(COMB0_REG_PHYPINS_LINK_CTRL2(combo0->top_reg)); + val &= ~(PHY0_IDDQ_EN_SEL | PHY0_IDDQ_EN_SOFT); + writel(val, COMB0_REG_PHYPINS_LINK_CTRL2(combo0->top_reg)); + + phy_writew(combo0->phy_reg + (0xE005 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0xE006 << 1), 0x1124); + if (combo0->orientation == TYPEC_ORIENTATION_REVERSE) { + /* Restore DP Normal orientation register configuration */ + phy_writew(combo0->phy_reg + (0x4500 << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x4700 << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x4502 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x4702 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x4503 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x4703 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x8400 << 1), 0x091D); + phy_writew(combo0->phy_reg + (0x8600 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8402 << 1), 0x0900); + phy_writew(combo0->phy_reg + (0x8602 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8403 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8603 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8406 << 1), 0x010F); + phy_writew(combo0->phy_reg + (0x8606 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x44EA << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x46EA << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x8508 << 1), 0x0009); + phy_writew(combo0->phy_reg + (0x8708 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8510 << 1), 0x0009); + phy_writew(combo0->phy_reg + (0x8710 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8518 << 1), 0x000D); + phy_writew(combo0->phy_reg + (0x8718 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x4440 << 1), 0x2A84); + phy_writew(combo0->phy_reg + (0x4640 << 1), 0x2A84); + phy_writew(combo0->phy_reg + (0x44C6 << 1), 0x00A3); + phy_writew(combo0->phy_reg + (0x46C6 << 1), 0x00A3); + phy_writew(combo0->phy_reg + (0x4450 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x4650 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x444C << 1), 0x001C); + phy_writew(combo0->phy_reg + (0x464C << 1), 0x001C); + } else { + /* Restore DP Reverse orientation register configuration */ + phy_writew(combo0->phy_reg + (0x4100 << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x4300 << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x4102 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x4302 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x4103 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x4303 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x8000 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8200 << 1), 0x091D); + phy_writew(combo0->phy_reg + (0x8002 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8202 << 1), 0x0900); + phy_writew(combo0->phy_reg + (0x8003 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8203 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8006 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8206 << 1), 0x010F); + phy_writew(combo0->phy_reg + (0x40EA << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x42EA << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x8108 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8308 << 1), 0x0009); + phy_writew(combo0->phy_reg + (0x8110 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8310 << 1), 0x0009); + phy_writew(combo0->phy_reg + (0x8118 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8318 << 1), 0x000D); + phy_writew(combo0->phy_reg + (0x4040 << 1), 0x2A84); + phy_writew(combo0->phy_reg + (0x4240 << 1), 0x2A84); + phy_writew(combo0->phy_reg + (0x40C6 << 1), 0x00A3); + phy_writew(combo0->phy_reg + (0x42C6 << 1), 0x00A3); + phy_writew(combo0->phy_reg + (0x4050 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x4250 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x404C << 1), 0x001C); + phy_writew(combo0->phy_reg + (0x424C << 1), 0x001C); + } + + phy_writel(combo0->top_reg + 0x4, 0x01100001); + phy_writew(combo0->phy_reg + (0x0022 << 1), 0x001A); + phy_writew(combo0->phy_reg + (0x002A << 1), 0x0034); + phy_writew(combo0->phy_reg + (0x002C << 1), 0x00DA); + phy_writew(combo0->phy_reg + (0x0032 << 1), 0x0034); + phy_writew(combo0->phy_reg + (0x0034 << 1), 0x00DA); + phy_writew(combo0->phy_reg + (0x0064 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x0065 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x0074 << 1), 0x001A); + phy_writew(combo0->phy_reg + (0x0104 << 1), 0x0020); + phy_writew(combo0->phy_reg + (0x0105 << 1), 0x0007); + phy_writew(combo0->phy_reg + (0x010C << 1), 0x0020); + phy_writew(combo0->phy_reg + (0x010D << 1), 0x0007); + phy_writew(combo0->phy_reg + (0x0114 << 1), 0x030C); + phy_writew(combo0->phy_reg + (0x0115 << 1), 0x0007); + phy_writew(combo0->phy_reg + (0x0124 << 1), 0x0007); + phy_writew(combo0->phy_reg + (0x0125 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x0126 << 1), 0x000F); + phy_writew(combo0->phy_reg + (0x0128 << 1), 0x0132); + phy_writew(combo0->phy_reg + (0x8044 << 1), 0x001A); + phy_writew(combo0->phy_reg + (0x8244 << 1), 0x001A); + phy_writew(combo0->phy_reg + (0x8444 << 1), 0x001A); + phy_writew(combo0->phy_reg + (0x8644 << 1), 0x001A); + phy_writew(combo0->phy_reg + (0x8045 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x8245 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x8445 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x8645 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x804C << 1), 0x001A); + phy_writew(combo0->phy_reg + (0x824C << 1), 0x001A); + phy_writew(combo0->phy_reg + (0x844C << 1), 0x001A); + phy_writew(combo0->phy_reg + (0x864C << 1), 0x001A); + phy_writew(combo0->phy_reg + (0x804D << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x824D << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x844D << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x864D << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x4123 << 1), 0x0A28); + phy_writew(combo0->phy_reg + (0x4323 << 1), 0x0A28); + phy_writew(combo0->phy_reg + (0x4523 << 1), 0x0A28); + phy_writew(combo0->phy_reg + (0x4723 << 1), 0x0A28); + phy_writew(combo0->phy_reg + (0x01A1 << 1), 0x8600); + phy_writew(combo0->phy_reg + (0x01C1 << 1), 0x0701); + + if (combo0->orientation == TYPEC_ORIENTATION_REVERSE) { + /* Restore USB Normal orientation register configuration */ + phy_writew(combo0->phy_reg + (0x40E5 << 1), 0x0012); + phy_writew(combo0->phy_reg + (0x42E5 << 1), 0x0012); + phy_writew(combo0->phy_reg + (0x44E6 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x46E6 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x44E7 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x46E7 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x44E5 << 1), 0x0012); + phy_writew(combo0->phy_reg + (0x46E5 << 1), 0x0012); + + phy_writew(combo0->phy_reg + (0x44E5 << 1), 0x0041); + phy_writew(combo0->phy_reg + (0x46E5 << 1), 0x0041); + phy_writew(combo0->phy_reg + (0x40E6 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x42E6 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x40E7 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x42E7 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x40E5 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0x42E5 << 1), 0x0019); + } else { + /* Restore USB Reverse orientation register configuration */ + phy_writew(combo0->phy_reg + (0x44E5 << 1), 0x0012); + phy_writew(combo0->phy_reg + (0x46E5 << 1), 0x0012); + phy_writew(combo0->phy_reg + (0x40E6 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x42E6 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x40E7 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x42E7 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x40E5 << 1), 0x0012); + phy_writew(combo0->phy_reg + (0x42E5 << 1), 0x0012); + + phy_writew(combo0->phy_reg + (0x40E5 << 1), 0x0041); + phy_writew(combo0->phy_reg + (0x42E5 << 1), 0x0041); + phy_writew(combo0->phy_reg + (0x44E6 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x46E6 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x44E7 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x46E7 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x44E5 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0x46E5 << 1), 0x0019); + } + + phy_writew(combo0->phy_reg + (0x0094 << 1), 0x0004); + phy_writew(combo0->phy_reg + (0x00D4 << 1), 0x0004); + phy_writew(combo0->phy_reg + (0x01A4 << 1), 0x0509); + phy_writew(combo0->phy_reg + (0x01C4 << 1), 0x0509); + phy_writew(combo0->phy_reg + (0x01A5 << 1), 0x0F00); + phy_writew(combo0->phy_reg + (0x01C5 << 1), 0x0F00); + phy_writew(combo0->phy_reg + (0x01A6 << 1), 0x0F08); + phy_writew(combo0->phy_reg + (0x01C6 << 1), 0x0F08); + phy_writew(combo0->phy_reg + (0x0090 << 1), 0x0180); + phy_writew(combo0->phy_reg + (0x00D0 << 1), 0x019F); + phy_writew(combo0->phy_reg + (0x0091 << 1), 0x9D8A); + phy_writew(combo0->phy_reg + (0x00D1 << 1), 0x6276); + phy_writew(combo0->phy_reg + (0x0092 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x00D2 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x0093 << 1), 0x0102); + phy_writew(combo0->phy_reg + (0x00D3 << 1), 0x0116); + phy_writew(combo0->phy_reg + (0x01A0 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x01C0 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x0098 << 1), 0x0001); + if (combo0->ssc_en) + phy_writew(combo0->phy_reg + (0x00D8 << 1), 0x0001); + else + phy_writew(combo0->phy_reg + (0x00D8 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x0099 << 1), 0x045F); + phy_writew(combo0->phy_reg + (0x00D9 << 1), 0x04C4); + phy_writew(combo0->phy_reg + (0x009A << 1), 0x006B); + phy_writew(combo0->phy_reg + (0x00DA << 1), 0x006A); + phy_writew(combo0->phy_reg + (0x009B << 1), 0x0004); + phy_writew(combo0->phy_reg + (0x00DB << 1), 0x0004); + phy_writew(combo0->phy_reg + (0x0084 << 1), 0x0104); + phy_writew(combo0->phy_reg + (0x00C4 << 1), 0x0104); + phy_writew(combo0->phy_reg + (0x0085 << 1), 0x0005); + phy_writew(combo0->phy_reg + (0x00C5 << 1), 0x0005); + phy_writew(combo0->phy_reg + (0x0086 << 1), 0x0337); + phy_writew(combo0->phy_reg + (0x00C6 << 1), 0x0337); + phy_writew(combo0->phy_reg + (0x0088 << 1), 0x0335); + phy_writew(combo0->phy_reg + (0x00C8 << 1), 0x0335); + phy_writew(combo0->phy_reg + (0x0082 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x00C2 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x009C << 1), 0x00CF); + phy_writew(combo0->phy_reg + (0x00DC << 1), 0x00CF); + phy_writew(combo0->phy_reg + (0x009E << 1), 0x00CE); + phy_writew(combo0->phy_reg + (0x00DE << 1), 0x00CE); + phy_writew(combo0->phy_reg + (0x009F << 1), 0x0005); + phy_writew(combo0->phy_reg + (0x00DF << 1), 0x0005); + + val = readl(COMB0_REG_PHYPINS_LINK_CTRL0(combo0->top_reg)); + if (combo0->orientation == TYPEC_ORIENTATION_REVERSE) + val |= PHY0_TYPEC_CONN_DIR; + else + val &= ~PHY0_TYPEC_CONN_DIR; + writel(val, COMB0_REG_PHYPINS_LINK_CTRL0(combo0->top_reg)); + + phy_writew(combo0->phy_reg + (0xC010 << 1), 0x5100); + phy_writew(combo0->phy_reg + (0xC011 << 1), 0x0100); + phy_writew(combo0->phy_reg + (0xC018 << 1), 0x0A0A); + phy_writew(combo0->phy_reg + (0xC01A << 1), 0x1000); + phy_writew(combo0->phy_reg + (0xC01B << 1), 0x0010); + phy_writew(combo0->phy_reg + (0x0041 << 1), 0x8200); + phy_writew(combo0->phy_reg + (0x0047 << 1), 0x8200); + + if (combo0->orientation == TYPEC_ORIENTATION_REVERSE) { + /* Restore USB Normal orientation register configuration */ + phy_writew(combo0->phy_reg + (0x4100 << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x4101 << 1), 0x04AF); + phy_writew(combo0->phy_reg + (0x4102 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x4103 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x8200 << 1), 0x091D); + phy_writew(combo0->phy_reg + (0x8201 << 1), 0x091D); + phy_writew(combo0->phy_reg + (0x8202 << 1), 0x0900); + phy_writew(combo0->phy_reg + (0x8203 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x4040 << 1), 0x2A84); + phy_writew(combo0->phy_reg + (0x404D << 1), 0x0011); + phy_writew(combo0->phy_reg + (0x8290 << 1), 0x000C); + phy_writew(combo0->phy_reg + (0x8308 << 1), 0x0009); + phy_writew(combo0->phy_reg + (0x8349 << 1), 0x0c02); + phy_writew(combo0->phy_reg + (0x8377 << 1), 0x06F6); + phy_writew(combo0->phy_reg + (0x8378 << 1), 0x4606); + phy_writew(combo0->phy_reg + (0x42EB << 1), 0x0006); + phy_writew(combo0->phy_reg + (0x8371 << 1), 0x0519); + phy_writew(combo0->phy_reg + (0x8372 << 1), 0x0519); + phy_writew(combo0->phy_reg + (0x83E8 << 1), 0x1002); + phy_writew(combo0->phy_reg + (0x83E5 << 1), 0x0B98); + phy_writew(combo0->phy_reg + (0x83E2 << 1), 0x0C01); + phy_writew(combo0->phy_reg + (0x83E3 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x83F5 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x83F4 << 1), 0x0311); + phy_writew(combo0->phy_reg + (0x83FF << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x81FF << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8280 << 1), 0x010a); + phy_writew(combo0->phy_reg + (0x8282 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x40EA << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x42EA << 1), 0x00FF); + + phy_writew(combo0->phy_reg + (0x4700 << 1), 0x02FF); + phy_writew(combo0->phy_reg + (0x4701 << 1), 0x06AF); + phy_writew(combo0->phy_reg + (0x4702 << 1), 0x06AE); + phy_writew(combo0->phy_reg + (0x4703 << 1), 0x06AE); + phy_writew(combo0->phy_reg + (0x8400 << 1), 0x0D1D); + phy_writew(combo0->phy_reg + (0x8401 << 1), 0x0D1D); + phy_writew(combo0->phy_reg + (0x8402 << 1), 0x0D00); + phy_writew(combo0->phy_reg + (0x8403 << 1), 0x0500); + phy_writew(combo0->phy_reg + (0x4640 << 1), 0x2A82); + phy_writew(combo0->phy_reg + (0x464D << 1), 0x0014); + phy_writew(combo0->phy_reg + (0x8490 << 1), 0x0013); + phy_writew(combo0->phy_reg + (0x8508 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8549 << 1), 0x0C02); + phy_writew(combo0->phy_reg + (0x8577 << 1), 0x0330); + phy_writew(combo0->phy_reg + (0x8578 << 1), 0x0300); + phy_writew(combo0->phy_reg + (0x44EB << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x8571 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0x8572 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0x85E8 << 1), 0x1004); + phy_writew(combo0->phy_reg + (0x85E5 << 1), 0x00F9); + phy_writew(combo0->phy_reg + (0x85E2 << 1), 0x0C01); + phy_writew(combo0->phy_reg + (0x85E3 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x85F5 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x85F4 << 1), 0x0031); + phy_writew(combo0->phy_reg + (0x85FF << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x87FF << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x8480 << 1), 0x018C); + phy_writew(combo0->phy_reg + (0x8482 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x46EA << 1), 0x000F); + phy_writew(combo0->phy_reg + (0x44EA << 1), 0x00F0); + } else { + /* Restore USB Reverse orientation register configuration */ + phy_writew(combo0->phy_reg + (0x4700 << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x4701 << 1), 0x04AF); + phy_writew(combo0->phy_reg + (0x4702 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x4703 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x8400 << 1), 0x091D); + phy_writew(combo0->phy_reg + (0x8401 << 1), 0x091D); + phy_writew(combo0->phy_reg + (0x8402 << 1), 0x0900); + phy_writew(combo0->phy_reg + (0x8403 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x4640 << 1), 0x2A84); + phy_writew(combo0->phy_reg + (0x464D << 1), 0x0011); + phy_writew(combo0->phy_reg + (0x8490 << 1), 0x000C); + phy_writew(combo0->phy_reg + (0x8508 << 1), 0x0009); + phy_writew(combo0->phy_reg + (0x8549 << 1), 0x0C02); + phy_writew(combo0->phy_reg + (0x8577 << 1), 0x06F6); + phy_writew(combo0->phy_reg + (0x8578 << 1), 0x4606); + phy_writew(combo0->phy_reg + (0x44EB << 1), 0x0006); + phy_writew(combo0->phy_reg + (0x8571 << 1), 0x0519); + phy_writew(combo0->phy_reg + (0x8572 << 1), 0x0519); + phy_writew(combo0->phy_reg + (0x85E8 << 1), 0x1002); + phy_writew(combo0->phy_reg + (0x85E5 << 1), 0x0B98); + phy_writew(combo0->phy_reg + (0x85E2 << 1), 0x0C01); + phy_writew(combo0->phy_reg + (0x85E3 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x85F5 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x85F4 << 1), 0x0311); + phy_writew(combo0->phy_reg + (0x85FF << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x87FF << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8480 << 1), 0x010A); + phy_writew(combo0->phy_reg + (0x8482 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x46EA << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x44EA << 1), 0x00FF); + + phy_writew(combo0->phy_reg + (0x4100 << 1), 0x02FF); + phy_writew(combo0->phy_reg + (0x4101 << 1), 0x06AF); + phy_writew(combo0->phy_reg + (0x4102 << 1), 0x06AE); + phy_writew(combo0->phy_reg + (0x4103 << 1), 0x06AE); + phy_writew(combo0->phy_reg + (0x8200 << 1), 0x0D1D); + phy_writew(combo0->phy_reg + (0x8201 << 1), 0x0D1D); + phy_writew(combo0->phy_reg + (0x8202 << 1), 0x0D00); + phy_writew(combo0->phy_reg + (0x8203 << 1), 0x0500); + phy_writew(combo0->phy_reg + (0x4040 << 1), 0x2A82); + phy_writew(combo0->phy_reg + (0x404D << 1), 0x0014); + phy_writew(combo0->phy_reg + (0x8290 << 1), 0x0013); + phy_writew(combo0->phy_reg + (0x8308 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8349 << 1), 0x0C02); + phy_writew(combo0->phy_reg + (0x8377 << 1), 0x0330); + phy_writew(combo0->phy_reg + (0x8378 << 1), 0x0300); + phy_writew(combo0->phy_reg + (0x42EB << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x8371 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0x8372 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0x83E8 << 1), 0x1004); + phy_writew(combo0->phy_reg + (0x83E5 << 1), 0x00F9); + phy_writew(combo0->phy_reg + (0x83E2 << 1), 0x0C01); + phy_writew(combo0->phy_reg + (0x83E3 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x83F5 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x83F4 << 1), 0x0031); + phy_writew(combo0->phy_reg + (0x83FF << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x81FF << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x8280 << 1), 0x018C); + phy_writew(combo0->phy_reg + (0x8282 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x40EA << 1), 0x000F); + phy_writew(combo0->phy_reg + (0x42EA << 1), 0x00F0); + } + + phy_writew(combo0->phy_reg + (0xE005 << 1), 0x000B); + phy_writew(combo0->phy_reg + (0xE006 << 1), 0x2224); + if (combo0->orientation == TYPEC_ORIENTATION_REVERSE) { + phy_writew(combo0->phy_reg + (0x4100 << 1), 0x00FB); + phy_writew(combo0->phy_reg + (0x4300 << 1), 0x00FB); + phy_writew(combo0->phy_reg + (0x4102 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4302 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4103 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4303 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x8000 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8200 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8002 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8202 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8003 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8203 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8006 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8206 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x40EA << 1), 0x000F); + phy_writew(combo0->phy_reg + (0x42EA << 1), 0x000F); + phy_writew(combo0->phy_reg + (0x8108 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8308 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8110 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8310 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8118 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8318 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x4040 << 1), 0x08A4); + phy_writew(combo0->phy_reg + (0x4240 << 1), 0x08A4); + phy_writew(combo0->phy_reg + (0x40C6 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x42C6 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x4050 << 1), 0x002A); + phy_writew(combo0->phy_reg + (0x4250 << 1), 0x002A); + phy_writew(combo0->phy_reg + (0x404C << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x424C << 1), 0x0000); + } else { + phy_writew(combo0->phy_reg + (0x4500 << 1), 0x00FB); + phy_writew(combo0->phy_reg + (0x4700 << 1), 0x00FB); + phy_writew(combo0->phy_reg + (0x4502 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4702 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4503 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x4703 << 1), 0x04AA); + phy_writew(combo0->phy_reg + (0x8400 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8600 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8402 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8602 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8403 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8603 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8406 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8606 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x44EA << 1), 0x000F); + phy_writew(combo0->phy_reg + (0x46EA << 1), 0x000F); + phy_writew(combo0->phy_reg + (0x8508 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8708 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8510 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8710 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8518 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8718 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x4440 << 1), 0x08A4); + phy_writew(combo0->phy_reg + (0x4640 << 1), 0x08A4); + phy_writew(combo0->phy_reg + (0x44C6 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x46C6 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x4450 << 1), 0x002A); + phy_writew(combo0->phy_reg + (0x4650 << 1), 0x002A); + phy_writew(combo0->phy_reg + (0x444C << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x464C << 1), 0x0000); + } + phy_writew(combo0->phy_reg + (0x0103 << 1), 0x007F); + phy_writew(combo0->phy_reg + (0x010B << 1), 0x007F); + + return 0; +} + +static int sunxi_cadence_combo0_dp_phy_init(struct phy *phy) +{ + struct sunxi_cadence_combophy *combo0 = phy_get_drvdata(phy); + + mutex_lock(&combo0->phy_lock); + + combo0_dp_phy_init(combo0); + + combo0->mode |= (1 << PHY_TYPE_DP); + + mutex_unlock(&combo0->phy_lock); + + return 0; +} + +static int sunxi_cadence_combo0_dp_phy_exit(struct phy *phy) +{ + struct sunxi_cadence_combophy *combo0 = phy_get_drvdata(phy); + + mutex_lock(&combo0->phy_lock); + + combo0_dp_phy_exit(combo0); + + mutex_unlock(&combo0->phy_lock); + + return 0; +} + +static int sunxi_cadence_combo0_dp_phy_power_on(struct phy *phy) +{ + struct sunxi_cadence_combophy *combo0 = phy_get_drvdata(phy); + + mutex_lock(&combo0->phy_lock); + + combo0_dp_phy_power_on(combo0); + + mutex_unlock(&combo0->phy_lock); + + return 0; +} + +static int sunxi_cadence_combo0_dp_phy_power_off(struct phy *phy) +{ + struct sunxi_cadence_combophy *combo0 = phy_get_drvdata(phy); + + mutex_lock(&combo0->phy_lock); + + combo0_dp_phy_power_off(combo0); + + mutex_unlock(&combo0->phy_lock); + + return 0; +} + +static int sunxi_cadence_combo0_dp_phy_configure(struct phy *phy, union phy_configure_opts *opts) +{ + struct sunxi_cadence_combophy *combo0 = phy_get_drvdata(phy); + struct phy_configure_opts_dp *dp_config = &opts->dp; + int ret = 0; + + mutex_lock(&combo0->phy_lock); + + combo0->mode |= (1 << PHY_TYPE_DP); + + ret = combo0_dp_set_ssc(combo0, dp_config); + if (ret < 0) + goto OUT; + + ret = combo0_dp_set_lanes(combo0, dp_config); + if (ret < 0) + goto OUT; + + ret = combo0_dp_set_rate(combo0, dp_config); + if (ret < 0) + goto OUT; + + ret = combo0_dp_set_sw_pre(combo0, dp_config); + if (ret < 0) + goto OUT; + +OUT: + mutex_unlock(&combo0->phy_lock); + return ret; +} + +static int sunxi_cadence_combo0_dp_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode) +{ + struct sunxi_cadence_combophy *combo0 = phy_get_drvdata(phy); + struct sunxi_cadence_phy *sunxi_cphy = combo0->sunxi_cphy; + enum typec_orientation orientation = COMBO0_TYPEC_ORIENTATION(submode); + unsigned long state = COMBO0_TYPEC_MODE(submode); + __u8 hpd_state = COMBO0_HPD_STATE(submode); + u32 val = 0; + + pr_debug("mode: %d, submode: %d, orientation: %s, state: %s", mode, submode, + typec_orientation_name[orientation], typec_state_name[state]); + + mutex_lock(&combo0->phy_lock); + + switch (mode) { + case PHY_MODE_DP: + if ((combo0->orientation != orientation) || (combo0->state != state)) { + if ((state == TYPEC_DP_STATE_D) || (state == TYPEC_DP_STATE_F)) { + combo0->usb_dp_state = true; + combo0_dp_phy_reset(combo0, false); + combo0_configure_usb_dp(combo0); + combo0_dp_phy_reset(combo0, true); + } else { + combo0->usb_dp_state = false; + } + + // set lane remap + //combo0_dp_lane_remap(combo0, orientation); + if (combo0->orientation != orientation) { + val = readl(COMB0_REG_PHYPINS_LINK_CTRL0(combo0->top_reg)); + if (orientation == TYPEC_ORIENTATION_REVERSE) + val |= PHY0_TYPEC_CONN_DIR; + else + val &= ~PHY0_TYPEC_CONN_DIR; + writel(val, COMB0_REG_PHYPINS_LINK_CTRL0(combo0->top_reg)); + } + /* something such as lane cnt limit are need in future */ + if (combo0->state != state) { + //TODO + } + } + + if (combo0->hpd_state != hpd_state) { + if (sunxi_cphy->extcon) + extcon_set_state_sync(sunxi_cphy->extcon, EXTCON_DISP_DP, hpd_state); + combo0->hpd_state = hpd_state; + } + combo0->orientation = orientation; + combo0->state = state; + + break; + default: + dev_warn(sunxi_cphy->dev, "%s Configuration mismatch.\n", combo0->name); + break; + } + + mutex_unlock(&combo0->phy_lock); + return 0; +} + +/* use for typec STATE_D/F device, to limit its lane cnt max to 2 */ +static int sunxi_cadence_combo0_dp_phy_validate(struct phy *phy, enum phy_mode mode, + int submode, union phy_configure_opts *opts) +{ + struct sunxi_cadence_combophy *combo0 = phy_get_drvdata(phy); + struct phy_configure_opts_dp *dp_config = &opts->dp; + + /* if not typec STATE_D/F application, ignore it */ + if (!combo0->usb_dp_state) + return 0; + + switch (mode) { + case PHY_MODE_DP: + if (dp_config->set_lanes) { + if (dp_config->lanes > MAX_STATE_D_F_LANE_CNT) + return -EPROTONOSUPPORT; + else + return 0; + } + break; + default: + break; + } + + return 0; +} + +static const struct phy_ops combo0_dp_phy_ops = { + .power_on = sunxi_cadence_combo0_dp_phy_power_on, + .power_off = sunxi_cadence_combo0_dp_phy_power_off, + .init = sunxi_cadence_combo0_dp_phy_init, + .exit = sunxi_cadence_combo0_dp_phy_exit, + .configure = sunxi_cadence_combo0_dp_phy_configure, + .set_mode = sunxi_cadence_combo0_dp_phy_set_mode, + .validate = sunxi_cadence_combo0_dp_phy_validate, + .owner = THIS_MODULE, +}; + +static void combo0_usb_clk_set(struct sunxi_cadence_phy *sunxi_cphy, bool enable) +{ + struct sunxi_cadence_combophy *combo0 = sunxi_cphy->combo0; + u32 val, tmp = 0; + + val = readl(COMB0_REG_PHYPINS_LINK_CTRL1(combo0->top_reg)); + tmp = PHY0_PMA_CMN_PLL1_REFCLK_SEL | PHY0_PMA_CMN_PLL0_REFCLK_SEL | PHY0_PMA_CMN_REFCLK_DIG_SEL; + if (enable) + val |= tmp; + else + val &= ~tmp; + writel(val, COMB0_REG_PHYPINS_LINK_CTRL1(combo0->top_reg)); + + val = readl(USB3P1_REG_PIPE_CLK_MAP_CTRL(sunxi_cphy->top_combo_reg)); + if (enable) { + val &= ~USB3P1_PIPE_CLK_MAP_MASK; + val |= USB3P1_PIPE_CLK_MAP(0); + } else { + val |= USB3P1_PIPE_CLK_MAP_MASK; + } + writel(val, USB3P1_REG_PIPE_CLK_MAP_CTRL(sunxi_cphy->top_combo_reg)); + + val = readl(USB3P1_REG_PIPE_RXD_MAP_CTRL(sunxi_cphy->top_combo_reg)); + if (enable) { + val &= ~USB3P1_PIPE_RXD_MAP_MASK; + val |= USB3P1_PIPE_RXD_MAP(0); + } else { + val |= USB3P1_PIPE_RXD_MAP_MASK; + } + writel(val, USB3P1_REG_PIPE_RXD_MAP_CTRL(sunxi_cphy->top_combo_reg)); +} + +static void combo0_usb_param_config(struct sunxi_cadence_combophy *combo0) +{ + struct sunxi_cadence_phy *sunxi_cphy = combo0->sunxi_cphy; + u32 val = 0; + + if (!COMBO_PHY_TYPE(combo0->mode, PHY_TYPE_USB3)) { + pr_warn("%s USB3 Configuration mismatch.\n", combo0->name); + return; + } + + if (sunxi_cphy->udp_supported) { + combo0_configure_usb_dp(combo0); + return; + } + + pr_debug("==================== orientation: %s DP configured: %s ====================", + typec_orientation_name[combo0->orientation], COMBO_PHY_TYPE(combo0->mode, PHY_TYPE_DP) ? "yes" : "no"); + + if (COMBO_PHY_TYPE(combo0->mode, PHY_TYPE_DP)) { + + val = readl(COMB0_REG_PHYPINS_LINK_CTRL0(combo0->top_reg)); + val &= ~PHY0_PIPE_LINK_RESET_N_USB3P1_MASK; + writel(val, COMB0_REG_PHYPINS_LINK_CTRL0(combo0->top_reg)); + + phy_writew(combo0->phy_reg + (0xE005 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0xE006 << 1), 0x1124); + + /* Restore DP Reverse orientation register configuration */ + phy_writew(combo0->phy_reg + (0x4100 << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x4300 << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x4102 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x4302 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x4103 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x4303 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x8000 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8200 << 1), 0x091D); + phy_writew(combo0->phy_reg + (0x8002 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8202 << 1), 0x0900); + phy_writew(combo0->phy_reg + (0x8003 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8203 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8006 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8206 << 1), 0x010F); + phy_writew(combo0->phy_reg + (0x40EA << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x42EA << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x8108 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8308 << 1), 0x0009); + phy_writew(combo0->phy_reg + (0x8110 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8310 << 1), 0x0009); + phy_writew(combo0->phy_reg + (0x8118 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8318 << 1), 0x000D); + phy_writew(combo0->phy_reg + (0x4040 << 1), 0x2A84); + phy_writew(combo0->phy_reg + (0x4240 << 1), 0x2A84); + phy_writew(combo0->phy_reg + (0x40C6 << 1), 0x00A3); + phy_writew(combo0->phy_reg + (0x42C6 << 1), 0x00A3); + phy_writew(combo0->phy_reg + (0x4050 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x4250 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x404C << 1), 0x001C); + phy_writew(combo0->phy_reg + (0x424C << 1), 0x001C); + + /* Restore DP Normal orientation register configuration */ + phy_writew(combo0->phy_reg + (0x4500 << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x4700 << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x4502 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x4702 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x4503 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x4703 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x8400 << 1), 0x091D); + phy_writew(combo0->phy_reg + (0x8600 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8402 << 1), 0x0900); + phy_writew(combo0->phy_reg + (0x8602 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8403 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8603 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8406 << 1), 0x010F); + phy_writew(combo0->phy_reg + (0x8606 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x44EA << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x46EA << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x8508 << 1), 0x0009); + phy_writew(combo0->phy_reg + (0x8708 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8510 << 1), 0x0009); + phy_writew(combo0->phy_reg + (0x8710 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8518 << 1), 0x000D); + phy_writew(combo0->phy_reg + (0x8718 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x4440 << 1), 0x2A84); + phy_writew(combo0->phy_reg + (0x4640 << 1), 0x2A84); + phy_writew(combo0->phy_reg + (0x44C6 << 1), 0x00A3); + phy_writew(combo0->phy_reg + (0x46C6 << 1), 0x00A3); + phy_writew(combo0->phy_reg + (0x4450 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x4650 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x444C << 1), 0x001C); + phy_writew(combo0->phy_reg + (0x464C << 1), 0x001C); + } + + phy_writew(combo0->phy_reg + (0x0022 << 1), 0x001A); + phy_writew(combo0->phy_reg + (0x002A << 1), 0x0034); + phy_writew(combo0->phy_reg + (0x002C << 1), 0x00DA); + phy_writew(combo0->phy_reg + (0x0032 << 1), 0x0034); + phy_writew(combo0->phy_reg + (0x0034 << 1), 0x00DA); + phy_writew(combo0->phy_reg + (0x0064 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x0065 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x0074 << 1), 0x001A); + phy_writew(combo0->phy_reg + (0x0104 << 1), 0x0020); + phy_writew(combo0->phy_reg + (0x0105 << 1), 0x0007); + phy_writew(combo0->phy_reg + (0x010C << 1), 0x0020); + phy_writew(combo0->phy_reg + (0x010D << 1), 0x0007); + phy_writew(combo0->phy_reg + (0x0114 << 1), 0x030C); + phy_writew(combo0->phy_reg + (0x0115 << 1), 0x0007); + phy_writew(combo0->phy_reg + (0x0124 << 1), 0x0007); + phy_writew(combo0->phy_reg + (0x0125 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x0126 << 1), 0x000F); + phy_writew(combo0->phy_reg + (0x0128 << 1), 0x0132); + phy_writew(combo0->phy_reg + (0x8044 << 1), 0x001A); + phy_writew(combo0->phy_reg + (0x8244 << 1), 0x001A); + phy_writew(combo0->phy_reg + (0x8444 << 1), 0x001A); + phy_writew(combo0->phy_reg + (0x8644 << 1), 0x001A); + phy_writew(combo0->phy_reg + (0x8045 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x8245 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x8445 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x8645 << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x804C << 1), 0x001A); + phy_writew(combo0->phy_reg + (0x824C << 1), 0x001A); + phy_writew(combo0->phy_reg + (0x844C << 1), 0x001A); + phy_writew(combo0->phy_reg + (0x864C << 1), 0x001A); + phy_writew(combo0->phy_reg + (0x804D << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x824D << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x844D << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x864D << 1), 0x0082); + phy_writew(combo0->phy_reg + (0x4123 << 1), 0x0A28); + phy_writew(combo0->phy_reg + (0x4323 << 1), 0x0A28); + phy_writew(combo0->phy_reg + (0x4523 << 1), 0x0A28); + phy_writew(combo0->phy_reg + (0x4723 << 1), 0x0A28); + phy_writew(combo0->phy_reg + (0x01A1 << 1), 0x8600); + phy_writew(combo0->phy_reg + (0x01C1 << 1), 0x0601); + + if (combo0->orientation == TYPEC_ORIENTATION_REVERSE) { + /* Restore USB Normal orientation register configuration */ + phy_writew(combo0->phy_reg + (0x40E5 << 1), 0x0012); + phy_writew(combo0->phy_reg + (0x42E5 << 1), 0x0012); + phy_writew(combo0->phy_reg + (0x44E6 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x46E6 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x44E7 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x46E7 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x44E5 << 1), 0x0012); + phy_writew(combo0->phy_reg + (0x46E5 << 1), 0x0012); + + phy_writew(combo0->phy_reg + (0x44E5 << 1), 0x0041); + phy_writew(combo0->phy_reg + (0x46E5 << 1), 0x0041); + phy_writew(combo0->phy_reg + (0x40E6 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x42E6 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x40E7 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x42E7 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x40E5 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0x42E5 << 1), 0x0019); + } else { + /* Restore USB Reverse orientation register configuration */ + phy_writew(combo0->phy_reg + (0x44E5 << 1), 0x0012); + phy_writew(combo0->phy_reg + (0x46E5 << 1), 0x0012); + phy_writew(combo0->phy_reg + (0x40E6 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x42E6 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x40E7 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x42E7 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x40E5 << 1), 0x0012); + phy_writew(combo0->phy_reg + (0x42E5 << 1), 0x0012); + + phy_writew(combo0->phy_reg + (0x40E5 << 1), 0x0041); + phy_writew(combo0->phy_reg + (0x42E5 << 1), 0x0041); + phy_writew(combo0->phy_reg + (0x44E6 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x46E6 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x44E7 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x46E7 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x44E5 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0x46E5 << 1), 0x0019); + } + + phy_writew(combo0->phy_reg + (0x0094 << 1), 0x0004); + phy_writew(combo0->phy_reg + (0x00D4 << 1), 0x0004); + phy_writew(combo0->phy_reg + (0x01A4 << 1), 0x0509); + phy_writew(combo0->phy_reg + (0x01C4 << 1), 0x0509); + phy_writew(combo0->phy_reg + (0x01A5 << 1), 0x0F00); + phy_writew(combo0->phy_reg + (0x01C5 << 1), 0x0F00); + phy_writew(combo0->phy_reg + (0x01A6 << 1), 0x0F08); + phy_writew(combo0->phy_reg + (0x01C6 << 1), 0x0F08); + phy_writew(combo0->phy_reg + (0x0090 << 1), 0x0180); + phy_writew(combo0->phy_reg + (0x00D0 << 1), 0x019F); + phy_writew(combo0->phy_reg + (0x0091 << 1), 0x9D8A); + phy_writew(combo0->phy_reg + (0x00D1 << 1), 0x6276); + phy_writew(combo0->phy_reg + (0x0092 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x00D2 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x0093 << 1), 0x0102); + phy_writew(combo0->phy_reg + (0x00D3 << 1), 0x0116); + phy_writew(combo0->phy_reg + (0x01A0 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x01C0 << 1), 0x0002); + + if (combo0->configuration & PHY_CFG_SSC) { + phy_writew(combo0->phy_reg + (0x0098 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x00D8 << 1), 0x0001); + } else { + phy_writew(combo0->phy_reg + (0x0098 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x00D8 << 1), 0x0002); + } + + phy_writew(combo0->phy_reg + (0x0099 << 1), 0x045F); + phy_writew(combo0->phy_reg + (0x00D9 << 1), 0x04C4); + phy_writew(combo0->phy_reg + (0x009A << 1), 0x006B); + phy_writew(combo0->phy_reg + (0x00DA << 1), 0x006A); + phy_writew(combo0->phy_reg + (0x009B << 1), 0x0004); + phy_writew(combo0->phy_reg + (0x00DB << 1), 0x0004); + phy_writew(combo0->phy_reg + (0x0084 << 1), 0x0104); + phy_writew(combo0->phy_reg + (0x00C4 << 1), 0x0104); + phy_writew(combo0->phy_reg + (0x0085 << 1), 0x0005); + phy_writew(combo0->phy_reg + (0x00C5 << 1), 0x0005); + phy_writew(combo0->phy_reg + (0x0086 << 1), 0x0337); + phy_writew(combo0->phy_reg + (0x00C6 << 1), 0x0337); + phy_writew(combo0->phy_reg + (0x0088 << 1), 0x0335); + phy_writew(combo0->phy_reg + (0x00C8 << 1), 0x0335); + phy_writew(combo0->phy_reg + (0x0082 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x00C2 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x009C << 1), 0x00CF); + phy_writew(combo0->phy_reg + (0x00DC << 1), 0x00CF); + phy_writew(combo0->phy_reg + (0x009E << 1), 0x00CE); + phy_writew(combo0->phy_reg + (0x00DE << 1), 0x00CE); + phy_writew(combo0->phy_reg + (0x009F << 1), 0x0005); + phy_writew(combo0->phy_reg + (0x00DF << 1), 0x0005); + + val = readl(COMB0_REG_PHYPINS_LINK_CTRL0(combo0->top_reg)); + if (combo0->orientation == TYPEC_ORIENTATION_REVERSE) + val |= PHY0_TYPEC_CONN_DIR; + else + val &= ~PHY0_TYPEC_CONN_DIR; + writel(val, COMB0_REG_PHYPINS_LINK_CTRL0(combo0->top_reg)); + + phy_writew(combo0->phy_reg + (0xC010 << 1), 0x5100); + phy_writew(combo0->phy_reg + (0xC011 << 1), 0x0100); + phy_writew(combo0->phy_reg + (0xC011 << 1), 0x010F); + phy_writew(combo0->phy_reg + (0xC018 << 1), 0x0A0A); + phy_writew(combo0->phy_reg + (0xC01A << 1), 0x1008); + phy_writew(combo0->phy_reg + (0xC01B << 1), 0x0010); + phy_writew(combo0->phy_reg + (0x0041 << 1), 0x8200); + phy_writew(combo0->phy_reg + (0x0047 << 1), 0x8200); + + if (combo0->orientation == TYPEC_ORIENTATION_REVERSE) { + /* Restore USB Normal orientation register configuration */ + phy_writew(combo0->phy_reg + (0x4100 << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x4101 << 1), 0x04AF); + phy_writew(combo0->phy_reg + (0x4102 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x4103 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x8200 << 1), 0x091D); + phy_writew(combo0->phy_reg + (0x8201 << 1), 0x091D); + phy_writew(combo0->phy_reg + (0x8202 << 1), 0x0900); + phy_writew(combo0->phy_reg + (0x8203 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x4040 << 1), 0x2A84); + phy_writew(combo0->phy_reg + (0x404D << 1), 0x0011); + phy_writew(combo0->phy_reg + (0x8290 << 1), 0x000C); + phy_writew(combo0->phy_reg + (0x8308 << 1), 0x0009); + phy_writew(combo0->phy_reg + (0x8349 << 1), 0x0c02); + phy_writew(combo0->phy_reg + (0x8377 << 1), 0x06F6); + phy_writew(combo0->phy_reg + (0x8378 << 1), 0x4606); + phy_writew(combo0->phy_reg + (0x42EB << 1), 0x0006); + phy_writew(combo0->phy_reg + (0x8371 << 1), 0x0519); + phy_writew(combo0->phy_reg + (0x8372 << 1), 0x0519); + phy_writew(combo0->phy_reg + (0x83E8 << 1), 0x1002); + phy_writew(combo0->phy_reg + (0x83E5 << 1), 0x0B98); + phy_writew(combo0->phy_reg + (0x83E2 << 1), 0x0C01); + phy_writew(combo0->phy_reg + (0x83E3 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x83F5 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x83F4 << 1), 0x0311); + phy_writew(combo0->phy_reg + (0x83FF << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x81FF << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8280 << 1), 0x010a); + phy_writew(combo0->phy_reg + (0x8282 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x40EA << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x42EA << 1), 0x00FF); + + phy_writew(combo0->phy_reg + (0x4700 << 1), 0x02FF); + phy_writew(combo0->phy_reg + (0x4701 << 1), 0x06AF); + phy_writew(combo0->phy_reg + (0x4702 << 1), 0x06AE); + phy_writew(combo0->phy_reg + (0x4703 << 1), 0x06AE); + phy_writew(combo0->phy_reg + (0x8400 << 1), 0x0D1D); + phy_writew(combo0->phy_reg + (0x8401 << 1), 0x0D1D); + phy_writew(combo0->phy_reg + (0x8402 << 1), 0x0D00); + phy_writew(combo0->phy_reg + (0x8403 << 1), 0x0500); + phy_writew(combo0->phy_reg + (0x4640 << 1), 0x2A82); + phy_writew(combo0->phy_reg + (0x464D << 1), 0x0014); + phy_writew(combo0->phy_reg + (0x8490 << 1), 0x0013); + phy_writew(combo0->phy_reg + (0x8508 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8549 << 1), 0x0C02); + phy_writew(combo0->phy_reg + (0x8577 << 1), 0x0330); + phy_writew(combo0->phy_reg + (0x8578 << 1), 0x0300); + phy_writew(combo0->phy_reg + (0x44EB << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x8571 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0x8572 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0x85E8 << 1), 0x1004); + phy_writew(combo0->phy_reg + (0x85E5 << 1), 0x00F9); + phy_writew(combo0->phy_reg + (0x85E2 << 1), 0x0C01); + phy_writew(combo0->phy_reg + (0x85E3 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x85F5 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x85F4 << 1), 0x0031); + phy_writew(combo0->phy_reg + (0x85FF << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x87FF << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x8480 << 1), 0x018C); + phy_writew(combo0->phy_reg + (0x8482 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x46EA << 1), 0x000F); + phy_writew(combo0->phy_reg + (0x44EA << 1), 0x00F0); + } else { + /* Restore USB Reverse orientation register configuration */ + phy_writew(combo0->phy_reg + (0x4700 << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x4701 << 1), 0x04AF); + phy_writew(combo0->phy_reg + (0x4702 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x4703 << 1), 0x04AE); + phy_writew(combo0->phy_reg + (0x8400 << 1), 0x091D); + phy_writew(combo0->phy_reg + (0x8401 << 1), 0x091D); + phy_writew(combo0->phy_reg + (0x8402 << 1), 0x0900); + phy_writew(combo0->phy_reg + (0x8403 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x4640 << 1), 0x2A84); + phy_writew(combo0->phy_reg + (0x464D << 1), 0x0011); + phy_writew(combo0->phy_reg + (0x8490 << 1), 0x000C); + phy_writew(combo0->phy_reg + (0x8508 << 1), 0x0009); + phy_writew(combo0->phy_reg + (0x8549 << 1), 0x0C02); + phy_writew(combo0->phy_reg + (0x8577 << 1), 0x06F6); + phy_writew(combo0->phy_reg + (0x8578 << 1), 0x4606); + phy_writew(combo0->phy_reg + (0x44EB << 1), 0x0006); + phy_writew(combo0->phy_reg + (0x8571 << 1), 0x0519); + phy_writew(combo0->phy_reg + (0x8572 << 1), 0x0519); + phy_writew(combo0->phy_reg + (0x85E8 << 1), 0x1002); + phy_writew(combo0->phy_reg + (0x85E5 << 1), 0x0B98); + phy_writew(combo0->phy_reg + (0x85E2 << 1), 0x0C01); + phy_writew(combo0->phy_reg + (0x85E3 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x85F5 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x85F4 << 1), 0x0311); + phy_writew(combo0->phy_reg + (0x85FF << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x87FF << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8480 << 1), 0x010A); + phy_writew(combo0->phy_reg + (0x8482 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x46EA << 1), 0x00FF); + phy_writew(combo0->phy_reg + (0x44EA << 1), 0x00FF); + + phy_writew(combo0->phy_reg + (0x4100 << 1), 0x02FF); + phy_writew(combo0->phy_reg + (0x4101 << 1), 0x06AF); + phy_writew(combo0->phy_reg + (0x4102 << 1), 0x06AE); + phy_writew(combo0->phy_reg + (0x4103 << 1), 0x06AE); + phy_writew(combo0->phy_reg + (0x8200 << 1), 0x0D1D); + phy_writew(combo0->phy_reg + (0x8201 << 1), 0x0D1D); + phy_writew(combo0->phy_reg + (0x8202 << 1), 0x0D00); + phy_writew(combo0->phy_reg + (0x8203 << 1), 0x0500); + phy_writew(combo0->phy_reg + (0x4040 << 1), 0x2A82); + phy_writew(combo0->phy_reg + (0x404D << 1), 0x0014); + phy_writew(combo0->phy_reg + (0x8290 << 1), 0x0013); + phy_writew(combo0->phy_reg + (0x8308 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x8349 << 1), 0x0C02); + phy_writew(combo0->phy_reg + (0x8377 << 1), 0x0330); + phy_writew(combo0->phy_reg + (0x8378 << 1), 0x0300); + phy_writew(combo0->phy_reg + (0x42EB << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x8371 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0x8372 << 1), 0x0019); + phy_writew(combo0->phy_reg + (0x83E8 << 1), 0x1004); + phy_writew(combo0->phy_reg + (0x83E5 << 1), 0x00F9); + phy_writew(combo0->phy_reg + (0x83E2 << 1), 0x0C01); + phy_writew(combo0->phy_reg + (0x83E3 << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x83F5 << 1), 0x0000); + phy_writew(combo0->phy_reg + (0x83F4 << 1), 0x0031); + phy_writew(combo0->phy_reg + (0x83FF << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x81FF << 1), 0x0002); + phy_writew(combo0->phy_reg + (0x8280 << 1), 0x018C); + phy_writew(combo0->phy_reg + (0x8282 << 1), 0x0003); + phy_writew(combo0->phy_reg + (0x40EA << 1), 0x000F); + phy_writew(combo0->phy_reg + (0x42EA << 1), 0x00F0); + } + + phy_writew(combo0->phy_reg + (0xE003 << 1), 0x0001); + phy_writew(combo0->phy_reg + (0x0103 << 1), 0x007F); + phy_writew(combo0->phy_reg + (0x010B << 1), 0x007F); +} + +static void combo0_usb_phy_reset(struct sunxi_cadence_combophy *combo0, bool enable) +{ + u32 val, cnt = 0; + struct sunxi_cadence_phy *sunxi_cphy = combo0->sunxi_cphy; + + if (enable) { + val = readl(COMB0_REG_PHYPINS_LINK_CTRL0(combo0->top_reg)); + val &= ~PHY0_RESET_N; + writel(val, COMB0_REG_PHYPINS_LINK_CTRL0(combo0->top_reg)); + + mdelay(1); + + val = readl(COMB0_REG_PHYPINS_LINK_CTRL0(combo0->top_reg)); + val |= PHY0_RESET_N; + writel(val, COMB0_REG_PHYPINS_LINK_CTRL0(combo0->top_reg)); + } + + /* restore dp lane convertion configuration */ + val = readl(DP_RAWS_TXDW_CONV_CRTL(sunxi_cphy->top_combo_reg)); + val |= DP_PHY_CFG_DATA_EN_BIT; + val |= DP_PHY_CFG_LN_EN_MASK; + val |= DP_PHY_CFG_LN_RSTN_MASK; + writel(val, DP_RAWS_TXDW_CONV_CRTL(sunxi_cphy->top_combo_reg)); + + while (enable) { + val = readl(COMB0_REG_PHYPINS_LINK_CTRL0(combo0->top_reg)); + if (++cnt > 1000) { + pr_warn("wrn: %s wait phy reset timeout\n", combo0->name); + break; + } + if (val & PHY0_RESET_N) + break; + udelay(1); + } + + mdelay(1); + + val = readl(COMB0_REG_PHYPINS_LINK_CTRL0(combo0->top_reg)); + if (enable) + val |= PHY0_PIPE_LINK_RESET_N_SOFT; + else + val &= ~PHY0_PIPE_LINK_RESET_N_SOFT; + writel(val, COMB0_REG_PHYPINS_LINK_CTRL0(combo0->top_reg)); + + val = readl(COMB0_REG_PHYPINS_LINK_CTRL3(combo0->top_reg)); + if (enable) + val |= PHY0_PMA_XCVR_PLLCLK_EN_LN; + else + val &= ~PHY0_PMA_XCVR_PLLCLK_EN_LN; + writel(val, COMB0_REG_PHYPINS_LINK_CTRL3(combo0->top_reg)); + + cnt = 0; + while (enable) { + val = readl(COMB0_REG_PHYPINS_LINK_CTRL3(combo0->top_reg)); + if (++cnt > 1000) { + pr_warn("wrn: %s wait pll enable timeout\n", combo0->name); + break; + } + if (val & PHY0_PMA_XCVR_PLLCLK_EN_LN) + break; + udelay(1); + } + + val = readl(COMB0_REG_PHYPINS_LINK_CTRL3(combo0->top_reg)); + if (enable) + val |= PHY0_PMA_XCVR_POWER_STATE_REQ_LN(1); + else + val &= ~PHY0_PMA_XCVR_POWER_STATE_REQ_LN_MASK; + writel(val, COMB0_REG_PHYPINS_LINK_CTRL3(combo0->top_reg)); + + val = readl(COMB0_REG_PHYPINS_LINK_CTRL0(combo0->top_reg)); + if (enable) + val |= PHY0_DP_LINK_RESET_N_SOFT; + else + val &= ~PHY0_DP_LINK_RESET_N_SOFT; + writel(val, COMB0_REG_PHYPINS_LINK_CTRL0(combo0->top_reg)); + + val = readl(COMB0_REG_PHYPINS_LINK_STUS0(combo0->top_reg)); + if (!(val & PHY0_PMA_CMN_READY)) { + mdelay(10); + + val = phy_readw(combo0->phy_reg + (0xC008 << 1)); + if (enable) + val &= ~(0xF << 8); + else + val |= (0xF << 8); + phy_writew(combo0->phy_reg + (0xC008 << 1), val); + + cnt = 0; + while (enable) { + val = readl(COMB0_REG_PHYPINS_LINK_STUS0(combo0->top_reg)); + if (++cnt > 1000) { + pr_warn("wrn: %s wait pma ready timeout\n", combo0->name); + break; + } + if (val & PHY0_PMA_CMN_READY) + break; + udelay(5); + } + } +} + +static void combo0_usb_pwr_set(struct sunxi_cadence_phy *sunxi_cphy, bool enable) +{ + struct sunxi_cadence_combophy *combo0 = sunxi_cphy->combo0; + u32 val; + + dev_info(sunxi_cphy->dev, "%s set power %s\n", combo0->name, enable ? "OFF" : "ON"); + val = readl(COMB0_REG_PHYPINS_LINK_CTRL2(combo0->top_reg)); + if (enable) + val |= PHY0_IDDQ_EN_SEL; + else + val &= ~PHY0_IDDQ_EN_SEL; + writel(val, COMB0_REG_PHYPINS_LINK_CTRL2(combo0->top_reg)); + + /* IDDQ mode enable */ + val = readl(COMB0_REG_PHYPINS_LINK_CTRL2(combo0->top_reg)); + if (enable) + val |= PHY0_IDDQ_EN_SOFT; + else + val &= ~PHY0_IDDQ_EN_SOFT; + writel(val, COMB0_REG_PHYPINS_LINK_CTRL2(combo0->top_reg)); +} + +static int combo0_usb_phy_init(struct sunxi_cadence_combophy *combo0) +{ + struct sunxi_cadence_phy *sunxi_cphy = combo0->sunxi_cphy; + int ret; + + combo0->configuration = PHY_CFG_SSC; + + if (sunxi_cphy->avdd_h_regulator) + ret = regulator_enable(sunxi_cphy->avdd_h_regulator); + + if (sunxi_cphy->avdd_d_regulator) + ret = regulator_enable(sunxi_cphy->avdd_d_regulator); + + if (sunxi_cphy->avdd_c_regulator) + ret = regulator_enable(sunxi_cphy->avdd_c_regulator); + + combo_usb2_clk_set(sunxi_cphy, true); + + if (sunxi_cphy->serdes_clk) { + ret = clk_set_rate(sunxi_cphy->serdes_clk, 100000000); + if (ret) { + dev_err(sunxi_cphy->dev, "set serdes_clk rate 100MHz err, return %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(sunxi_cphy->serdes_clk); + if (ret) { + dev_err(sunxi_cphy->dev, "enable serdes_clk err, return %d\n", ret); + return ret; + } + } + + if (sunxi_cphy->dcxo_serdes0_clk) { + ret = clk_prepare_enable(sunxi_cphy->dcxo_serdes0_clk); + if (ret) { + dev_err(sunxi_cphy->dev, "enable dcxo_serdes0_clk err, return %d\n", ret); + return ret; + } + } + + if (combo0->clk) { + ret = clk_prepare_enable(combo0->clk); + if (ret) { + dev_err(sunxi_cphy->dev, "enable combo0 clk err, return %d\n", ret); + return ret; + } + } + + combo0_usb_pwr_set(sunxi_cphy, false); + combo0_usb_clk_set(sunxi_cphy, true); + combo0_usb_param_config(combo0); + combo0_usb_phy_reset(combo0, true); + + return 0; +} + +static int combo0_usb_phy_exit(struct sunxi_cadence_combophy *combo0) +{ + struct sunxi_cadence_phy *sunxi_cphy = combo0->sunxi_cphy; + + combo0_usb_clk_set(sunxi_cphy, false); + combo0_usb_phy_reset(combo0, false); + combo0_usb_pwr_set(sunxi_cphy, true); + + if (combo0->clk) + clk_disable_unprepare(combo0->clk); + + if (sunxi_cphy->dcxo_serdes0_clk) + clk_disable_unprepare(sunxi_cphy->dcxo_serdes0_clk); + + if (sunxi_cphy->serdes_clk) + clk_disable_unprepare(sunxi_cphy->serdes_clk); + + combo_usb2_clk_set(sunxi_cphy, false); + + if (sunxi_cphy->avdd_h_regulator) + regulator_disable(sunxi_cphy->avdd_h_regulator); + + if (sunxi_cphy->avdd_d_regulator) + regulator_disable(sunxi_cphy->avdd_d_regulator); + + if (sunxi_cphy->avdd_c_regulator) + regulator_disable(sunxi_cphy->avdd_c_regulator); + + return 0; +} + +static void combo0_usb_phy_power_on(struct sunxi_cadence_combophy *combo0) +{ +} + +static void combo0_usb_phy_power_off(struct sunxi_cadence_combophy *combo0) +{ +} + + +static int sunxi_cadence_combo0_usb_phy_init(struct phy *phy) +{ + struct sunxi_cadence_combophy *combo0 = phy_get_drvdata(phy); + + mutex_lock(&combo0->phy_lock); + + combo0->mode |= (1 << PHY_TYPE_USB3); + + combo0_usb_phy_init(combo0); + + mutex_unlock(&combo0->phy_lock); + + return 0; +} + +static int sunxi_cadence_combo0_usb_phy_exit(struct phy *phy) +{ + struct sunxi_cadence_combophy *combo0 = phy_get_drvdata(phy); + + mutex_lock(&combo0->phy_lock); + + combo0_usb_phy_exit(combo0); + + combo0->mode &= ~(1 << PHY_TYPE_USB3); + + mutex_unlock(&combo0->phy_lock); + + return 0; +} + +static int sunxi_cadence_combo0_usb_phy_power_on(struct phy *phy) +{ + struct sunxi_cadence_combophy *combo0 = phy_get_drvdata(phy); + + mutex_lock(&combo0->phy_lock); + + combo0_usb_phy_power_on(combo0); + + mutex_unlock(&combo0->phy_lock); + + return 0; +} + +static int sunxi_cadence_combo0_usb_phy_power_off(struct phy *phy) +{ + struct sunxi_cadence_combophy *combo0 = phy_get_drvdata(phy); + + mutex_lock(&combo0->phy_lock); + + combo0_usb_phy_power_off(combo0); + + mutex_unlock(&combo0->phy_lock); + + return 0; +} + +static int sunxi_cadence_combo0_usb_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode) +{ + struct sunxi_cadence_combophy *combo0 = phy_get_drvdata(phy); + struct sunxi_cadence_phy *sunxi_cphy = combo0->sunxi_cphy; + enum typec_orientation orientation = COMBO0_TYPEC_ORIENTATION(submode); + unsigned long state = COMBO0_TYPEC_MODE(submode); + + combo0->usb_dp_state = false; + pr_debug("mode: %d, submode: %d, orientation: %s, state: %s", mode, submode, + typec_orientation_name[orientation], typec_state_name[state]); + + mutex_lock(&combo0->phy_lock); + switch (mode) { + case PHY_MODE_USB_OTG: + if (sunxi_cphy->phy_switcher_quirk) + break; + if ((combo0->orientation != orientation) || (combo0->state != state)) { + combo0->orientation = orientation; + combo0->state = state; + + combo0_usb_param_config(combo0); + } + combo0->orientation = orientation; + combo0->state = state; + break; + case PHY_MODE_USB_HOST: + case PHY_MODE_USB_DEVICE: + break; + case PHY_MODE_USB_HOST_SS: + /** + * Condition 1: Power On or Reboot + * Condition 2: xhci_plat_probe Invalid + */ + if (combo0->orientation == TYPEC_ORIENTATION_NONE) { + combo0->orientation = orientation; + combo0_usb_param_config(combo0); + } else if (orientation != TYPEC_ORIENTATION_NONE) { + combo0->orientation = orientation; + } + break; + default: + dev_warn(sunxi_cphy->dev, "%s Configuration mismatch.\n", combo0->name); + break; + } + + mutex_unlock(&combo0->phy_lock); + + return 0; +} + +static const struct phy_ops combo0_usb_phy_ops = { + .power_on = sunxi_cadence_combo0_usb_phy_power_on, + .power_off = sunxi_cadence_combo0_usb_phy_power_off, + .init = sunxi_cadence_combo0_usb_phy_init, + .exit = sunxi_cadence_combo0_usb_phy_exit, + .set_mode = sunxi_cadence_combo0_usb_phy_set_mode, + .owner = THIS_MODULE, +}; + + +/*************************************************************************************************** + * Combo PHY1 Support. * + * - PIPE interface mode Compatible with PIPE Revision 4.4.1 * + * - PCIe Gen 1, 2 and 3, Maximum Speed up to 8Gbps * + * - USB3.1 SS and SS+, Maximum Speed up to 10Gbps * + * - Supports up to 1Lane * + **************************************************************************************************/ +static void combo1_usb_clk_set(struct sunxi_cadence_phy *sunxi_cphy, bool enable) +{ + struct sunxi_cadence_combophy *combo1 = sunxi_cphy->combo1; + u32 val, tmp = 0; + + val = readl(COMB1_REG_PHYPINS_LINK_CTRL1(combo1->top_reg)); + tmp = PHY1_PMA_CMN_PLL1_REFCLK_SEL | PHY1_PMA_CMN_PLL0_REFCLK_SEL | + PHY1_PMA_CMN_REFCLK_INT_MODE(3) | PHY1_PMA_CMN_REFCLK_MODE(2) | PHY1_PMA_CMN_REFCLK_DIG_SEL; + if (enable) + val |= tmp; + else + val &= ~tmp; + writel(val, COMB1_REG_PHYPINS_LINK_CTRL1(combo1->top_reg)); + + val = readl(COMB1_REG_PHYPINS_LANE0_CTRL0(combo1->top_reg)); + if (enable) { + val |= PHY1_L00_MODE(1); + } else { + val &= ~PHY1_L00_MODE_MASK; + } + writel(val, COMB1_REG_PHYPINS_LANE0_CTRL0(combo1->top_reg)); + + val = readl(COMB1_REG_PHYPINS_LINK_CTRL0(combo1->top_reg)); + if (enable) { + val |= PHY1_LINK_CFG_LN(1); + } else { + val &= ~PHY1_LINK_CFG_LN_MASK; + } + writel(val, COMB1_REG_PHYPINS_LINK_CTRL0(combo1->top_reg)); + + val = readl(SUBSYS_REG_DBGCTRL(sunxi_cphy->top_subsys_reg)); + if (enable) { + val |= COMBO1_PHY_REFCLK_AUTO_GATE_DIS; + } else { + val &= ~COMBO1_PHY_REFCLK_AUTO_GATE_DIS; + } + writel(val, SUBSYS_REG_DBGCTRL(sunxi_cphy->top_subsys_reg)); +} + +static void combo1_usb_param_config(struct sunxi_cadence_combophy *combo1) +{ + u32 val = 0; + phy_writew(combo1->phy_reg + (0x0022 << 1), 0x001A); + phy_writew(combo1->phy_reg + (0x002A << 1), 0x0034); + phy_writew(combo1->phy_reg + (0x002C << 1), 0x00DA); + phy_writew(combo1->phy_reg + (0x0032 << 1), 0x0034); + phy_writew(combo1->phy_reg + (0x0034 << 1), 0x00DA); + phy_writew(combo1->phy_reg + (0x0064 << 1), 0x0082); + phy_writew(combo1->phy_reg + (0x0065 << 1), 0x0082); + phy_writew(combo1->phy_reg + (0x0074 << 1), 0x001A); + phy_writew(combo1->phy_reg + (0x0104 << 1), 0x0020); + phy_writew(combo1->phy_reg + (0x0105 << 1), 0x0007); + phy_writew(combo1->phy_reg + (0x010C << 1), 0x0020); + phy_writew(combo1->phy_reg + (0x010D << 1), 0x0007); + phy_writew(combo1->phy_reg + (0x0114 << 1), 0x030C); + phy_writew(combo1->phy_reg + (0x0115 << 1), 0x0007); + phy_writew(combo1->phy_reg + (0x0124 << 1), 0x0007); + phy_writew(combo1->phy_reg + (0x0125 << 1), 0x0003); + phy_writew(combo1->phy_reg + (0x0126 << 1), 0x000F); + phy_writew(combo1->phy_reg + (0x0128 << 1), 0x0132); + phy_writew(combo1->phy_reg + (0x40C0 << 1), 0x0208); + phy_writew(combo1->phy_reg + (0x40C2 << 1), 0x009C); + phy_writew(combo1->phy_reg + (0x8044 << 1), 0x001A); + phy_writew(combo1->phy_reg + (0x8045 << 1), 0x0082); + phy_writew(combo1->phy_reg + (0x804C << 1), 0x001A); + phy_writew(combo1->phy_reg + (0x804D << 1), 0x0082); + phy_writew(combo1->phy_reg + (0x4123 << 1), 0x0A28); + + val = readl(COMB1_REG_PHYPINS_LANE0_CTRL0(combo1->top_reg)); + val &= ~PHY1_L00_MODE_MASK; + writel(val, COMB1_REG_PHYPINS_LANE0_CTRL0(combo1->top_reg)); + + val = readl(COMB1_REG_PHYPINS_LANE0_CTRL0(combo1->top_reg)); + val |= PHY1_L00_MODE(1); + writel(val, COMB1_REG_PHYPINS_LANE0_CTRL0(combo1->top_reg)); + + phy_writew(combo1->phy_reg + (0xC00E << 1), 0x0000); + phy_writew(combo1->phy_reg + (0x01A1 << 1), 0x8600); + phy_writew(combo1->phy_reg + (0x40E5 << 1), 0x0041); + phy_writew(combo1->phy_reg + (0x0094 << 1), 0x0004); + phy_writew(combo1->phy_reg + (0x00D4 << 1), 0x0004); + phy_writew(combo1->phy_reg + (0x01A4 << 1), 0x0509); + phy_writew(combo1->phy_reg + (0x01C4 << 1), 0x0509); + phy_writew(combo1->phy_reg + (0x01A5 << 1), 0x0F00); + phy_writew(combo1->phy_reg + (0x01C5 << 1), 0x0F00); + phy_writew(combo1->phy_reg + (0x01A6 << 1), 0x0F08); + phy_writew(combo1->phy_reg + (0x01C6 << 1), 0x0F08); + phy_writew(combo1->phy_reg + (0x0090 << 1), 0x0180); + phy_writew(combo1->phy_reg + (0x00D0 << 1), 0x0133); + phy_writew(combo1->phy_reg + (0x0091 << 1), 0x9D8A); + phy_writew(combo1->phy_reg + (0x00D1 << 1), 0xB13B); + phy_writew(combo1->phy_reg + (0x0092 << 1), 0x0002); + phy_writew(combo1->phy_reg + (0x00D2 << 1), 0x0002); + phy_writew(combo1->phy_reg + (0x0093 << 1), 0x0102); + phy_writew(combo1->phy_reg + (0x00D3 << 1), 0x00CE); + phy_writew(combo1->phy_reg + (0x01A0 << 1), 0x0002); + phy_writew(combo1->phy_reg + (0x01C0 << 1), 0x0002); + phy_writew(combo1->phy_reg + (0x0098 << 1), 0x0001); + phy_writew(combo1->phy_reg + (0x00D8 << 1), 0x0001); + phy_writew(combo1->phy_reg + (0x0099 << 1), 0x045F); + phy_writew(combo1->phy_reg + (0x00D9 << 1), 0x0399); + phy_writew(combo1->phy_reg + (0x009A << 1), 0x006B); + phy_writew(combo1->phy_reg + (0x00DA << 1), 0x0068); + phy_writew(combo1->phy_reg + (0x009B << 1), 0x0004); + phy_writew(combo1->phy_reg + (0x00DB << 1), 0x0004); + phy_writew(combo1->phy_reg + (0x0084 << 1), 0x0104); + phy_writew(combo1->phy_reg + (0x00C4 << 1), 0x0104); + phy_writew(combo1->phy_reg + (0x0085 << 1), 0x0005); + phy_writew(combo1->phy_reg + (0x00C5 << 1), 0x0005); + phy_writew(combo1->phy_reg + (0x0086 << 1), 0x0337); + phy_writew(combo1->phy_reg + (0x00C6 << 1), 0x0337); + phy_writew(combo1->phy_reg + (0x0088 << 1), 0x0335); + phy_writew(combo1->phy_reg + (0x00C8 << 1), 0x0335); + phy_writew(combo1->phy_reg + (0x0082 << 1), 0x0003); + phy_writew(combo1->phy_reg + (0x00C2 << 1), 0x0003); + phy_writew(combo1->phy_reg + (0x009C << 1), 0x00CF); + phy_writew(combo1->phy_reg + (0x00DC << 1), 0x00CF); + phy_writew(combo1->phy_reg + (0x009E << 1), 0x00CE); + phy_writew(combo1->phy_reg + (0x00DE << 1), 0x00CE); + phy_writew(combo1->phy_reg + (0x009F << 1), 0x0005); + phy_writew(combo1->phy_reg + (0x00DF << 1), 0x0005); + phy_writew(combo1->phy_reg + (0xC020 << 1), 0x0A0A); + phy_writew(combo1->phy_reg + (0xC022 << 1), 0x1008); + phy_writew(combo1->phy_reg + (0xC023 << 1), 0x0010); + phy_writew(combo1->phy_reg + (0x0041 << 1), 0x8200); + phy_writew(combo1->phy_reg + (0x0047 << 1), 0x8200); + phy_writew(combo1->phy_reg + (0x4100 << 1), 0x02FF); + phy_writew(combo1->phy_reg + (0x4101 << 1), 0x06AF); + phy_writew(combo1->phy_reg + (0x4102 << 1), 0x06AE); + phy_writew(combo1->phy_reg + (0x4103 << 1), 0x06AE); + phy_writew(combo1->phy_reg + (0x8000 << 1), 0x0D1D); + phy_writew(combo1->phy_reg + (0x8001 << 1), 0x0D1D); + phy_writew(combo1->phy_reg + (0x8002 << 1), 0x0D00); + phy_writew(combo1->phy_reg + (0x8003 << 1), 0x0500); + phy_writew(combo1->phy_reg + (0x4040 << 1), 0x2A82); + phy_writew(combo1->phy_reg + (0x404D << 1), 0x0014); + phy_writew(combo1->phy_reg + (0x8090 << 1), 0x0013); + phy_writew(combo1->phy_reg + (0x8108 << 1), 0x0000); + phy_writew(combo1->phy_reg + (0x8149 << 1), 0x0C02); + phy_writew(combo1->phy_reg + (0x8177 << 1), 0x0330); + phy_writew(combo1->phy_reg + (0x8178 << 1), 0x0300); + phy_writew(combo1->phy_reg + (0x40EB << 1), 0x0003); + phy_writew(combo1->phy_reg + (0x8171 << 1), 0x0019); + phy_writew(combo1->phy_reg + (0x8172 << 1), 0x0019); + phy_writew(combo1->phy_reg + (0x81E8 << 1), 0x1004); + phy_writew(combo1->phy_reg + (0x81E5 << 1), 0x00F9); + phy_writew(combo1->phy_reg + (0x81E2 << 1), 0x0C01); + phy_writew(combo1->phy_reg + (0x81E3 << 1), 0x0002); + phy_writew(combo1->phy_reg + (0x81F5 << 1), 0x0000); + phy_writew(combo1->phy_reg + (0x81F4 << 1), 0x0031); + phy_writew(combo1->phy_reg + (0x81FF << 1), 0x0001); + phy_writew(combo1->phy_reg + (0x8080 << 1), 0x018C); + phy_writew(combo1->phy_reg + (0x8082 << 1), 0x0003); +} + +static void combo1_usb_phy_reset(struct sunxi_cadence_combophy *combo1, bool enable) +{ + struct sunxi_cadence_phy *sunxi_cphy = combo1->sunxi_cphy; + u32 val, cnt = 0; + + val = readl(USB3P1_REG_PIPE_CLK_MAP_CTRL(sunxi_cphy->top_combo_reg)); + if (enable) { + val &= ~USB3P1_PIPE_CLK_MAP_MASK; + val |= USB3P1_PIPE_CLK_MAP(1); + } else { + val |= USB3P1_PIPE_CLK_MAP_MASK; + } + writel(val, USB3P1_REG_PIPE_CLK_MAP_CTRL(sunxi_cphy->top_combo_reg)); + + val = readl(USB3P1_REG_PIPE_RXD_MAP_CTRL(sunxi_cphy->top_combo_reg)); + if (enable) { + val &= ~USB3P1_PIPE_RXD_MAP_MASK; + val |= USB3P1_PIPE_RXD_MAP(1); + } else { + val |= USB3P1_PIPE_RXD_MAP_MASK; + } + writel(val, USB3P1_REG_PIPE_RXD_MAP_CTRL(sunxi_cphy->top_combo_reg)); + + val = readl(COMB1_REG_PHYPIPE_TXDATA_MAP_CTRL(sunxi_cphy->top_combo_reg)); + if (enable) { + val &= ~COMB1_PHY_LN0_PIPE_TXD_MAP_MASK; + val |= COMB1_PHY_LN0_PIPE_TXD_MAP(0); + } else { + val |= COMB1_PHY_LN0_PIPE_TXD_MAP_MASK; + } + writel(val, COMB1_REG_PHYPIPE_TXDATA_MAP_CTRL(sunxi_cphy->top_combo_reg)); + + val = readl(COMB1_REG_PHYPINS_LINK_CTRL0(combo1->top_reg)); + if (enable) + val |= PHY1_RESET_N; + else + val &= ~PHY1_RESET_N; + writel(val, COMB1_REG_PHYPINS_LINK_CTRL0(combo1->top_reg)); + + while (enable) { + val = readl(COMB1_REG_PHYPINS_LINK_CTRL0(combo1->top_reg)); + if (++cnt > 1000) { + pr_warn("wrn: %s wait phy reset timeout\n", combo1->name); + break; + } + if (val & PHY1_RESET_N) + break; + udelay(1); + } + + mdelay(1); + + val = readl(COMB1_REG_PHYPINS_LANE0_CTRL0(combo1->top_reg)); + if (enable) + val |= PHY1_L00_RESET_N_SW; + else + val &= ~PHY1_L00_RESET_N_SW; + writel(val, COMB1_REG_PHYPINS_LANE0_CTRL0(combo1->top_reg)); + + cnt = 0; + while (enable) { + val = readl(COMB1_REG_PHYPINS_LINK_STUS0(combo1->top_reg)); + if (++cnt > 1000) { + pr_warn("wrn: %s wait pma ready timeout\n", combo1->name); + break; + } + if (val & PHY1_PMA_CMN_READY) + break; + udelay(5); + } +} + +static void combo1_usb_pwr_set(struct sunxi_cadence_phy *sunxi_cphy, bool enable) +{ + struct sunxi_cadence_combophy *combo1 = sunxi_cphy->combo1; + u32 val; + + dev_info(sunxi_cphy->dev, "%s set power %s\n", combo1->name, enable ? "OFF" : "ON"); + val = readl(COMB1_REG_PHYPINS_LINK_CTRL3(combo1->top_reg)); + if (enable) + val |= PHY1_IDDQ_EN_SEL; + else + val &= ~PHY1_IDDQ_EN_SEL; + writel(val, COMB1_REG_PHYPINS_LINK_CTRL3(combo1->top_reg)); + + /* IDDQ mode enable */ + val = readl(COMB1_REG_PHYPINS_LINK_CTRL3(combo1->top_reg)); + if (enable) + val |= PHY1_IDDQ_EN; + else + val &= ~PHY1_IDDQ_EN; + writel(val, COMB1_REG_PHYPINS_LINK_CTRL3(combo1->top_reg)); +} + +static int sunxi_cadence_phy_combo1_usb3_init(struct sunxi_cadence_phy *sunxi_cphy) +{ + struct sunxi_cadence_combophy *combo1 = sunxi_cphy->combo1; + int ret; + + combo_usb2_clk_set(sunxi_cphy, true); + + if (sunxi_cphy->serdes_clk) { + ret = clk_set_rate(sunxi_cphy->serdes_clk, 100000000); + if (ret) { + dev_err(sunxi_cphy->dev, "set serdes_clk rate 100MHz err, return %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(sunxi_cphy->serdes_clk); + if (ret) { + dev_err(sunxi_cphy->dev, "enable serdes_clk err, return %d\n", ret); + return ret; + } + } + + if (sunxi_cphy->dcxo_serdes1_clk) { + ret = clk_prepare_enable(sunxi_cphy->dcxo_serdes1_clk); + if (ret) { + dev_err(sunxi_cphy->dev, "enable dcxo_serdes1_clk err, return %d\n", ret); + return ret; + } + } + + if (combo1->clk) { + ret = clk_prepare_enable(combo1->clk); + if (ret) { + dev_err(sunxi_cphy->dev, "enable combo1 clk err, return %d\n", ret); + return ret; + } + } + + combo1_usb_pwr_set(sunxi_cphy, false); + combo1_usb_clk_set(sunxi_cphy, true); + combo1_usb_param_config(combo1); + combo1_usb_phy_reset(combo1, true); + + return 0; +} + +static void sunxi_cadence_phy_combo1_usb3_exit(struct sunxi_cadence_phy *sunxi_cphy) +{ + struct sunxi_cadence_combophy *combo1 = sunxi_cphy->combo1; + + combo1_usb_clk_set(sunxi_cphy, false); + combo1_usb_phy_reset(combo1, false); + combo1_usb_pwr_set(sunxi_cphy, true); + + if (combo1->clk) + clk_disable_unprepare(combo1->clk); + + if (sunxi_cphy->dcxo_serdes1_clk) + clk_disable_unprepare(sunxi_cphy->dcxo_serdes1_clk); + + if (sunxi_cphy->serdes_clk) + clk_disable_unprepare(sunxi_cphy->serdes_clk); + + combo_usb2_clk_set(sunxi_cphy, false); +} + +static int sunxi_cadence_combo1_usb_phy_init(struct phy *phy) +{ + struct sunxi_cadence_combophy *combo1 = phy_get_drvdata(phy); + struct sunxi_cadence_phy *sunxi_cphy = combo1->sunxi_cphy; + int ret = 0; + + mutex_lock(&combo1->phy_lock); + + ret = sunxi_cadence_phy_combo1_usb3_init(sunxi_cphy); + + combo1->mode |= (1 << PHY_TYPE_USB3); + + mutex_unlock(&combo1->phy_lock); + + return ret; +} + +static int sunxi_cadence_combo1_usb_phy_exit(struct phy *phy) +{ + struct sunxi_cadence_combophy *combo1 = phy_get_drvdata(phy); + struct sunxi_cadence_phy *sunxi_cphy = combo1->sunxi_cphy; + + mutex_lock(&combo1->phy_lock); + + sunxi_cadence_phy_combo1_usb3_exit(sunxi_cphy); + + mutex_unlock(&combo1->phy_lock); + + return 0; +} + +static const struct phy_ops combo1_usb_phy_ops = { + .init = sunxi_cadence_combo1_usb_phy_init, + .exit = sunxi_cadence_combo1_usb_phy_exit, + .owner = THIS_MODULE, +}; + +static void sunxi_cadence_phy_pcie_phy_init(struct sunxi_cadence_phy *sunxi_cphy) +{ + struct sunxi_cadence_combophy *combo1 = sunxi_cphy->combo1; + u32 val; + + writel(0x01100001, combo1->top_reg + 0x4); + + writew(0x1a, combo1->phy_reg + 0x44); + + writew(0x34, combo1->phy_reg + 0x54); + writew(0xda, combo1->phy_reg + 0x58); + + writew(0x34, combo1->phy_reg + 0x64); + writew(0xda, combo1->phy_reg + 0x68); + + writew(0x82, combo1->phy_reg + 0xc8); + writew(0x82, combo1->phy_reg + 0xca); + + writew(0x1a, combo1->phy_reg + 0xe8); + + writew(0x20, combo1->phy_reg + 0x208); + writew(0x7, combo1->phy_reg + 0x20a); + writew(0x20, combo1->phy_reg + 0x218); + writew(0x7, combo1->phy_reg + 0x21a); + writew(0x30c, combo1->phy_reg + 0x228); + writew(0x7, combo1->phy_reg + 0x22a); + + writew(0x7, combo1->phy_reg + 0x248); + writew(0x3, combo1->phy_reg + 0x24a); + writew(0xf, combo1->phy_reg + 0x24c); + writew(0x132, combo1->phy_reg + 0x250); + + writew(0x208, combo1->phy_reg + 0x8180); + writew(0x9c, combo1->phy_reg + 0x8184); + + writew(0x1a, combo1->phy_reg + 0x10088); + writew(0x82, combo1->phy_reg + 0x1008a); + writew(0x1a, combo1->phy_reg + 0x10098); + writew(0x82, combo1->phy_reg + 0x1009a); + + writew(0xa28, combo1->phy_reg + 0x8246); + + val = readl(combo1->top_reg + 0x100); + val &= ~0x30; + writel(val, combo1->top_reg + 0x100); + + writew(0x4, combo1->phy_reg + 0x128); + writew(0x4, combo1->phy_reg + 0x148); + writew(0x4, combo1->phy_reg + 0x1a8); + + writew(0x509, combo1->phy_reg + 0x348); + writew(0x509, combo1->phy_reg + 0x368); + writew(0x509, combo1->phy_reg + 0x388); + writew(0xf00, combo1->phy_reg + 0x34a); + writew(0xf00, combo1->phy_reg + 0x36a); + writew(0xf00, combo1->phy_reg + 0x38a); + writew(0xf08, combo1->phy_reg + 0x34c); + writew(0xf08, combo1->phy_reg + 0x36c); + writew(0xf08, combo1->phy_reg + 0x38c); + + writew(0x180, combo1->phy_reg + 0x120); + writew(0x133, combo1->phy_reg + 0x140); + writew(0x133, combo1->phy_reg + 0x1a0); + writew(0x9d8a, combo1->phy_reg + 0x122); + writew(0xb13b, combo1->phy_reg + 0x142); + writew(0xb13b, combo1->phy_reg + 0x1a2); + writew(0x2, combo1->phy_reg + 0x124); + writew(0x2, combo1->phy_reg + 0x144); + writew(0x2, combo1->phy_reg + 0x1a4); + writew(0x102, combo1->phy_reg + 0x126); + writew(0xce, combo1->phy_reg + 0x146); + writew(0xce, combo1->phy_reg + 0x1a6); + + writew(0x22, combo1->phy_reg + 0x340); + writew(0x22, combo1->phy_reg + 0x360); + writew(0x22, combo1->phy_reg + 0x380); + + writew(0x1, combo1->phy_reg + 0x130); + writew(0x1, combo1->phy_reg + 0x150); + writew(0x1, combo1->phy_reg + 0x1b0); + writew(0x45f, combo1->phy_reg + 0x132); + writew(0x2f0, combo1->phy_reg + 0x152); + writew(0x399, combo1->phy_reg + 0x1b2); + writew(0x6b, combo1->phy_reg + 0x134); + writew(0x68, combo1->phy_reg + 0x154); + writew(0x68, combo1->phy_reg + 0x1b4); + writew(0x4, combo1->phy_reg + 0x136); + writew(0x4, combo1->phy_reg + 0x156); + writew(0x4, combo1->phy_reg + 0x1b6); + + writew(0x104, combo1->phy_reg + 0x108); + writew(0x104, combo1->phy_reg + 0x188); + writew(0x5, combo1->phy_reg + 0x10a); + writew(0x5, combo1->phy_reg + 0x18a); + writew(0x337, combo1->phy_reg + 0x10c); + writew(0x337, combo1->phy_reg + 0x18c); + writew(0x3dbe, combo1->phy_reg + 0x110); + writew(0x3dbe, combo1->phy_reg + 0x190); + writew(0x3, combo1->phy_reg + 0x104); + writew(0x3, combo1->phy_reg + 0x184); + + writew(0x14, combo1->phy_reg + 0x138); + writew(0x14, combo1->phy_reg + 0x1b8); + writew(0x192, combo1->phy_reg + 0x13c); + writew(0x192, combo1->phy_reg + 0x1bc); + writew(0x6, combo1->phy_reg + 0x13e); + writew(0x6, combo1->phy_reg + 0x1be); + + writew(0x0, combo1->phy_reg + 0x103c0); + writew(0x19, combo1->phy_reg + 0x102e2); + writew(0x19, combo1->phy_reg + 0x102e4); + + writew(0x1, combo1->phy_reg + 0x103fe); + + val = readl(combo1->phy_reg + 0x98); + val &= ~0x3; + val |= 0x2; + writel(val, combo1->phy_reg + 0x98); + + val = readl(combo1->phy_reg + 0xa8); + val &= ~0x3; + val |= 0x2; + writel(val, combo1->phy_reg + 0xa8); + + val = readl(combo1->phy_reg + 0xd8); + val &= ~0x3; + val |= 0x2; + writel(val, combo1->phy_reg + 0xd8); + + val = readl(combo1->top_reg); + writel(val | 0x1, combo1->top_reg); + val = readl(combo1->top_reg + 0x100); + writel(val | 0x1, combo1->top_reg + 0x100); + + val = readl(combo1->top_reg + 0x900); + while (true) { + udelay(100); + val = readl(combo1->top_reg + 0x900); + if (val & BIT(0)) + break; + } + + val = readw(combo1->phy_reg + 0xa0); + val |= 0x220; + val &= ~0x2; + val = 0x270; + writew(val, combo1->phy_reg + 0xa0); + + val = readw(combo1->phy_reg + 0x98); + val |= BIT(4); + writew(val, combo1->phy_reg + 0x98); + + val = readw(combo1->phy_reg + 0x18000); + val |= BIT(0); + writew(val, combo1->phy_reg + 0x18000); + + val = readl(combo1->top_reg + 0x4); + val |= BIT(28); + writel(val, combo1->top_reg + 0x4); + +} + +static int sunxi_cadence_phy_combo1_pcie_init(struct sunxi_cadence_phy *sunxi_cphy) +{ + int ret; + u32 val; + + ret = clk_set_rate(sunxi_cphy->serdes_clk, 100000000); + if (ret) + return ret; + + ret = clk_prepare_enable(sunxi_cphy->serdes_clk); + if (ret) + return ret; + + ret = clk_prepare_enable(sunxi_cphy->dcxo_serdes1_clk); + if (ret) + return ret; + + if (sunxi_cphy->serdes_reset) + reset_control_deassert(sunxi_cphy->serdes_reset); + + /* enable pcie */ + val = readl(sunxi_cphy->top_subsys_reg + SUBSYS_PCIE_BGR); + writel(val | SUBSYS_PCIE_GATING, sunxi_cphy->top_subsys_reg + SUBSYS_PCIE_BGR); + + /* disable combo1 auto gating */ + val = readl(sunxi_cphy->top_subsys_reg + SUBSYS_DBG_CTL); + writel(val | SUBSYS_DISABLE_COMBO1_AUTOGATING, sunxi_cphy->top_subsys_reg + SUBSYS_DBG_CTL); + + /* switch pipe to pcie */ + writel(SUBSYS_COMB1_PIPE_PCIE, sunxi_cphy->top_combo_reg + SUBSYS_COMB1_PIPE); + + sunxi_cadence_phy_pcie_phy_init(sunxi_cphy); + + return 0; +} + +static void sunxi_cadence_phy_combo1_pcie_exit(struct sunxi_cadence_phy *sunxi_cphy) +{ + if (sunxi_cphy->serdes_reset) + reset_control_assert(sunxi_cphy->serdes_reset); + + clk_disable_unprepare(sunxi_cphy->serdes_clk); + clk_disable_unprepare(sunxi_cphy->dcxo_serdes1_clk); +} + + +static int sunxi_cadence_combo1_pcie_phy_init(struct phy *phy) +{ + struct sunxi_cadence_combophy *combo1 = phy_get_drvdata(phy); + struct sunxi_cadence_phy *sunxi_cphy = combo1->sunxi_cphy; + int ret = 0; + + mutex_lock(&combo1->phy_lock); + + ret = sunxi_cadence_phy_combo1_pcie_init(sunxi_cphy); + + combo1->mode |= (1 << PHY_TYPE_PCIE); + + mutex_unlock(&combo1->phy_lock); + + return ret; +} + +static int sunxi_cadence_combo1_pcie_phy_exit(struct phy *phy) +{ + struct sunxi_cadence_combophy *combo1 = phy_get_drvdata(phy); + struct sunxi_cadence_phy *sunxi_cphy = combo1->sunxi_cphy; + + mutex_lock(&combo1->phy_lock); + + sunxi_cadence_phy_combo1_pcie_exit(sunxi_cphy); + + mutex_unlock(&combo1->phy_lock); + + return 0; +} + +static const struct phy_ops combo1_pcie_phy_ops = { + .init = sunxi_cadence_combo1_pcie_phy_init, + .exit = sunxi_cadence_combo1_pcie_phy_exit, + .owner = THIS_MODULE, +}; + +static int sunxi_cadence_aux_hpd_power_on(struct phy *phy) +{ + struct sunxi_cadence_combophy *aux_phy = phy_get_drvdata(phy); + + mutex_lock(&aux_phy->phy_lock); + + msleep(10); + /* aux phy init*/ + phy_set_lbits(aux_phy->top_reg + 0x00, 24, 4, 0x3); + phy_set_lbits(aux_phy->top_reg + 0x00, 20, 2, 0x3); + phy_set_lbits(aux_phy->top_reg + 0x00, 16, 2, 0x3); + phy_set_lbits(aux_phy->top_reg + 0x00, 12, 2, 0x1); + phy_set_lbits(aux_phy->top_reg + 0x00, 8, 1, 0x1); + + phy_set_lbits(aux_phy->top_reg + 0x04, 28, 1, 0x0); + phy_set_lbits(aux_phy->top_reg + 0x04, 24, 1, 0x0); + phy_set_lbits(aux_phy->top_reg + 0x04, 16, 1, 0x1); + phy_set_lbits(aux_phy->top_reg + 0x04, 8, 5, 0xe); + phy_set_lbits(aux_phy->top_reg + 0x04, 0, 5, 0xd); + + phy_writel(aux_phy->top_reg + 0x08, 0); + + phy_set_lbits(aux_phy->top_reg + 0x00, 4, 1, 0x1); + phy_set_lbits(aux_phy->top_reg + 0x00, 5, 1, 0x1); + phy_set_lbits(aux_phy->top_reg + 0x00, 0, 1, 0x1); + + mutex_unlock(&aux_phy->phy_lock); + + return 0; +} + +static int sunxi_cadence_aux_hpd_power_off(struct phy *phy) +{ + return 0; +} + +static int sunxi_cadence_aux_hpd_init(struct phy *phy) +{ + struct sunxi_cadence_combophy *aux_phy = phy_get_drvdata(phy); + struct sunxi_cadence_phy *sunxi_cphy = aux_phy->sunxi_cphy; + + mutex_lock(&aux_phy->phy_lock); + + msleep(10); + if (sunxi_cphy->serdes_clk) + clk_prepare_enable(sunxi_cphy->serdes_clk); + + if (sunxi_cphy->serdes_reset) + reset_control_deassert(sunxi_cphy->serdes_reset); + + aux_phy->mode |= (1 << PHY_TYPE_DP); + + mutex_unlock(&aux_phy->phy_lock); + + return 0; +} + +static int sunxi_cadence_aux_hpd_exit(struct phy *phy) +{ + struct sunxi_cadence_combophy *aux_phy = phy_get_drvdata(phy); + struct sunxi_cadence_phy *sunxi_cphy = aux_phy->sunxi_cphy; + + mutex_lock(&aux_phy->phy_lock); + + if (sunxi_cphy->serdes_reset) + reset_control_assert(sunxi_cphy->serdes_reset); + + if (sunxi_cphy->serdes_clk) + clk_disable_unprepare(sunxi_cphy->serdes_clk); + + mutex_unlock(&aux_phy->phy_lock); + + return 0; +} + +static const struct phy_ops aux_hpd_phy_ops = { + .power_on = sunxi_cadence_aux_hpd_power_on, + .power_off = sunxi_cadence_aux_hpd_power_off, + .init = sunxi_cadence_aux_hpd_init, + .exit = sunxi_cadence_aux_hpd_exit, + .owner = THIS_MODULE, +}; + +static const struct phy_ops *sunxi_cadence_node_to_ops(struct device_node *np) +{ + const struct phy_ops *ops = NULL; + + if (of_node_name_eq(np, "combo0-dp-phy")) { + ops = &combo0_dp_phy_ops; + } else if (of_node_name_eq(np, "combo0-usb-phy")) { + ops = &combo0_usb_phy_ops; + } else if (of_node_name_eq(np, "combo1-usb-phy")) { + ops = &combo1_usb_phy_ops; + } else if (of_node_name_eq(np, "combo1-pcie-phy")) { + ops = &combo1_pcie_phy_ops; + } else if (of_node_name_eq(np, "aux-hpd-phy")) { + ops = &aux_hpd_phy_ops; + } + + return ops; +} + +static int sunxi_cadence_phy_create(struct device *dev, struct device_node *np, + struct sunxi_cadence_combophy *combophy, enum phy_type_e type) +{ + struct sunxi_cadence_phy *sunxi_cphy = dev_get_drvdata(dev); + struct phy *phy = NULL; + const struct phy_ops *ops = NULL; + int ret; + u32 prop_val[MAX_LANE_CNT], i; + struct phy_provider *phy_provider = NULL; + struct device_node *child; + + switch (type) { + case COMBO_PHY0: + combophy->name = kstrdup_const("combophy0", GFP_KERNEL); + break; + case COMBO_PHY1: + combophy->name = kstrdup_const("combophy1", GFP_KERNEL); + break; + case AUX_HPD: + combophy->name = kstrdup_const("aux_hpd", GFP_KERNEL); + break; + default: + pr_err("not support phy type (%d)\n", type); + return -EINVAL; + } + + combophy->clk = devm_get_clk_from_child(dev, np, "phy-clk"); + if (IS_ERR(combophy->clk)) { + combophy->clk = NULL; + pr_debug("Maybe there is no clk for phy (%s)\n", combophy->name); + } + + combophy->bus_clk = devm_get_clk_from_child(dev, np, "phy-bus-clk"); + if (IS_ERR(combophy->bus_clk)) { + combophy->bus_clk = NULL; + pr_debug("Maybe there is no bus clk for phy (%s)\n", combophy->name); + } + + combophy->reset = of_reset_control_get(np, "phy_reset"); + if (IS_ERR(combophy->reset)) { + combophy->reset = NULL; + pr_debug("Maybe there is no reset for phy (%s)\n", combophy->name); + } + + if (combophy->reset) { + ret = devm_add_action_or_reset(dev, sunxi_cadence_phy_reset_control_put, + combophy->reset); + if (ret) + return ret; + } + + combophy->top_reg = of_iomap(np, 0); + if (!combophy->top_reg) + return -ENOMEM; + + combophy->phy_reg = of_iomap(np, 1); + if (!combophy->phy_reg) { + combophy->phy_reg = NULL; + pr_debug("Maybe there is no phy reg for %s\n", combophy->name); + } + + ret = of_property_read_u32_array(np, "lane_invert", prop_val, MAX_LANE_CNT); + if (ret == 0) { + for (i = 0; i < MAX_LANE_CNT; i++) + combophy->lane_invert[i] = prop_val[i]; + } else { + for (i = 0; i < MAX_LANE_CNT; i++) + combophy->lane_invert[i] = 0; + } + + ret = of_property_read_u32_array(np, "lane_remap", prop_val, MAX_LANE_CNT); + if (ret == 0) { + for (i = 0; i < MAX_LANE_CNT; i++) + combophy->lane_remap[i] = prop_val[i]; + } else { + for (i = 0; i < MAX_LANE_CNT; i++) + combophy->lane_remap[i] = i; + } + + ret = of_property_read_u32_array(np, "typec_remap", prop_val, MAX_LANE_CNT); + if (ret == 0) { + for (i = 0; i < MAX_LANE_CNT; i++) + combophy->typec_remap[i] = prop_val[i]; + } else { + for (i = 0; i < MAX_LANE_CNT; i++) + combophy->typec_remap[i] = i; + } + + //ret = of_property_read_u32(np, "ssc_en", &val); + //if (ret == 0) { + // combophy->ssc_en = val ? 1 : 0; + //} else { + // combophy->ssc_en = 0; + //} + + for_each_available_child_of_node(np, child) { + ops = sunxi_cadence_node_to_ops(child); + if (ops) { + phy = devm_phy_create(dev, child, ops); + if (IS_ERR(phy)) { + ret = PTR_ERR(phy); + dev_err(dev, "failed to create phy for %s, ret:%d\n", combophy->name, ret); + return ret; + } + + phy_set_drvdata(phy, combophy); + } else { + dev_err(dev, "failed to create phy for %s beacuse of ops missing\n", combophy->name); + } + } + + mutex_init(&combophy->phy_lock); + combophy->sunxi_cphy = sunxi_cphy; + combophy->mode = PHY_NONE; + combophy->usb_dp_state = false; + combophy->ssc_en = 0; + + pr_debug("phy[%s]: lane_invert[%d %d %d %d] lane_remap[%d %d %d %d] typec_remap[%d %d %d %d]\n", + combophy->name, combophy->lane_invert[0], combophy->lane_invert[1], + combophy->lane_invert[2], combophy->lane_invert[3], combophy->lane_remap[0], + combophy->lane_remap[1], combophy->lane_remap[2], combophy->lane_remap[3], + combophy->typec_remap[0], combophy->typec_remap[1], combophy->typec_remap[2], + combophy->typec_remap[3]); + + phy_provider = __devm_of_phy_provider_register(dev, np, THIS_MODULE, sunxi_cadence_phy_xlate); + if (IS_ERR(phy_provider)) + return PTR_ERR_OR_ZERO(phy_provider); + + return 0; +} + +/******************************************************************* + * Note: + * The features support by phy are listed as follow. From the table, + * we can know that PCIE/DP/DP_AUX has their only one specific conmophy + * the use, and USB3.1 can select between combo0 and combo1. + * + * So, we assume: DP only use combo0, PCIE only use combo1, USB may + * choose combo0 firstly. + * + * Some complex situation need to be solved later: + * How to compliance with all three module exist, USB/PCIE/DP ? + * _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + * | | USB3.1 | PCIE | DisplayPort | DP_AUX | + * |_ _ _ _ _ _ _|_ _ _ _ |_ _ _ |_ _ _ _ _ _ _|_ _ _ _ _| + * | Combophy0 | O | X | O | X | + * |_ _ _ _ _ _ _|_ _ _ _ |_ _ _ |_ _ _ _ _ _ _|_ _ _ _ _| + * | Combophy1 | O | O | X | X | + * |_ _ _ _ _ _ _|_ _ _ _ |_ _ _ |_ _ _ _ _ _ _|_ _ _ _ _| + * | AUX_HPD_PHY | X | X | X | O | + * |_ _ _ _ _ _ |_ _ _ _ |_ _ _ |_ _ _ _ _ _ _|_ _ _ _ _| + * + *******************************************************************/ +static struct phy *sunxi_cadence_phy_xlate(struct device *dev, + const struct of_phandle_args *args) +{ + struct phy *phy = NULL; + + phy = of_phy_simple_xlate(dev, args); + if (IS_ERR(phy)) { + pr_err("%s fail\n", __func__); + return phy; + } + + /* TODO: if need */ + + return phy; +} + +static int sunxi_cadence_phy_serdes_init(struct sunxi_cadence_phy *sunxi_cphy) +{ + int ret; + + if (!IS_ERR(sunxi_cphy->serdes1v8_supply)) { + ret = regulator_set_voltage(sunxi_cphy->serdes1v8_supply, 1800000, 1800000); + if (ret) { + dev_err(sunxi_cphy->dev, "set serdes 1v8-supply failed\n"); + } + + ret = regulator_enable(sunxi_cphy->serdes1v8_supply); + if (ret) { + dev_err(sunxi_cphy->dev, "enable serdes 1v8-supply failed\n"); + } + } + + if (sunxi_cphy->serdes_reset) { + ret = reset_control_deassert(sunxi_cphy->serdes_reset); + if (ret) { + dev_err(sunxi_cphy->dev, "reset serdes err, return %d\n", ret); + return ret; + } + } + + if (sunxi_cphy->usb_supported) + combo_usb_clk_set(sunxi_cphy, true); + + sunxi_cphy->vernum = top_ver_get(sunxi_cphy); + + return 0; +} + +static void sunxi_cadence_phy_serdes_exit(struct sunxi_cadence_phy *sunxi_cphy) +{ + if (sunxi_cphy->usb_supported) + combo_usb_clk_set(sunxi_cphy, false); + + if (sunxi_cphy->serdes_reset) + reset_control_assert(sunxi_cphy->serdes_reset); + + if (!IS_ERR(sunxi_cphy->serdes1v8_supply)) + regulator_disable(sunxi_cphy->serdes1v8_supply); +} + +static int sunxi_cadence_phy_parse_dt(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sunxi_cadence_phy *sunxi_cphy = dev_get_drvdata(dev); + struct device_node *child; + int ret; + + /* usb supported */ + sunxi_cphy->usb_supported = !device_property_read_bool(dev, "usb-disable"); + sunxi_cphy->udp_supported = !device_property_read_bool(dev, "udp-disable"); + sunxi_cphy->phy_switcher_quirk = device_property_read_bool(dev, "aw,phy-switcher-quirk"); + /* parse top register, which determide general configuration such as mode */ + sunxi_cphy->top_subsys_reg = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(sunxi_cphy->top_subsys_reg)) + return PTR_ERR(sunxi_cphy->top_subsys_reg); + + sunxi_cphy->top_combo_reg = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(sunxi_cphy->top_combo_reg)) + return PTR_ERR(sunxi_cphy->top_combo_reg); + + sunxi_cphy->serdes_clk = devm_clk_get(dev, "serdes-clk"); + if (IS_ERR(sunxi_cphy->serdes_clk)) { + return dev_err_probe(dev, PTR_ERR(sunxi_cphy->serdes_clk), + "failed to get serdes clock for sunxi cadence phy\n"); + } + + sunxi_cphy->dcxo_serdes0_clk = devm_clk_get(dev, "dcxo-serdes0-clk"); + if (IS_ERR(sunxi_cphy->dcxo_serdes0_clk)) { + return dev_err_probe(dev, PTR_ERR(sunxi_cphy->dcxo_serdes0_clk), + "failed to get dcxo serdes0 clock for sunxi cadence phy\n"); + } + + sunxi_cphy->dcxo_serdes1_clk = devm_clk_get(dev, "dcxo-serdes1-clk"); + if (IS_ERR(sunxi_cphy->dcxo_serdes1_clk)) { + return dev_err_probe(dev, PTR_ERR(sunxi_cphy->dcxo_serdes1_clk), + "failed to get dcxo serdes1 clock for sunxi cadence phy\n"); + } + + sunxi_cphy->serdes_reset = devm_reset_control_get_shared(dev, "serdes-reset"); + if (IS_ERR(sunxi_cphy->serdes_reset)) { + return dev_err_probe(dev, PTR_ERR(sunxi_cphy->serdes_reset), + "failed to get serdes reset for sunxi cadence phy\n"); + } + + /* serdes 1.8V AVDD-H-COMB0 supply */ + sunxi_cphy->serdes1v8_supply = devm_regulator_get_optional(dev, "serdes1v8"); + if (IS_ERR(sunxi_cphy->serdes1v8_supply)) + dev_err(dev, "get serdes 1v8-supply fail\n"); + +/* + sunxi_cphy->avdd_h_regulator = devm_regulator_get(dev, "avdd-h"); + if (IS_ERR(sunxi_cphy->avdd_h_regulator)) + pr_warn("AVDD-H supply is not found, maybe it's not need!"); + + sunxi_cphy->avdd_c_regulator = devm_regulator_get(dev, "avdd-c"); + if (IS_ERR(sunxi_cphy->avdd_c_regulator)) + pr_warn("AVDD-C supply is not found, maybe it's not need!"); + + sunxi_cphy->avdd_d_regulator = devm_regulator_get(dev, "avdd-d"); + if (IS_ERR(sunxi_cphy->avdd_d_regulator)) + pr_warn("AVDD-D supply is not found, maybe it's not need!"); + + sunxi_cphy->avdd_aux_regulator = devm_regulator_get(dev, "avdd-aux"); + if (IS_ERR(sunxi_cphy->avdd_aux_regulator)) + pr_warn("AVDD-AUX supply is not found, maybe it's not need!"); +*/ + + for_each_available_child_of_node(dev->of_node, child) { + if (of_node_name_eq(child, "combo-phy0")) { + sunxi_cphy->combo0 = devm_kzalloc(dev, sizeof(*sunxi_cphy->combo0), GFP_KERNEL); + if (!sunxi_cphy->combo0) + return -ENOMEM; + + /* create combophy0 */ + ret = sunxi_cadence_phy_create(dev, child, sunxi_cphy->combo0, COMBO_PHY0); + if (ret) { + dev_err(dev, "failed to create cadence combophy0, ret:%d\n", ret); + goto err_node_put; + } + sunxi_cphy->combo0->orientation = TYPEC_ORIENTATION_NONE; + + } else if (of_node_name_eq(child, "combo-phy1")) { + sunxi_cphy->combo1 = devm_kzalloc(dev, sizeof(*sunxi_cphy->combo1), GFP_KERNEL); + if (!sunxi_cphy->combo1) + return -ENOMEM; + + /* create combophy1 */ + ret = sunxi_cadence_phy_create(dev, child, sunxi_cphy->combo1, COMBO_PHY1); + if (ret) { + dev_err(dev, "failed to create cadence combophy1, ret:%d\n", ret); + goto err_node_put; + } + + } else if (of_node_name_eq(child, "aux-hpd")) { + sunxi_cphy->aux_hpd = devm_kzalloc(dev, sizeof(*sunxi_cphy->aux_hpd), GFP_KERNEL); + if (!sunxi_cphy->aux_hpd) + return -ENOMEM; + + /* create aux phy */ + ret = sunxi_cadence_phy_create(dev, child, sunxi_cphy->aux_hpd, AUX_HPD); + if (ret) { + dev_err(dev, "failed to create cadence aux-hpd phy, ret:%d\n", ret); + goto err_node_put; + } + } + } + + sunxi_cphy->extcon = devm_extcon_dev_allocate(dev, sunxi_cadence_cable); + if (IS_ERR_OR_NULL(sunxi_cphy->extcon)) { + dev_err(dev, "devm_extcon_dev_allocate fail\n"); + return PTR_ERR(sunxi_cphy->extcon); + } + devm_extcon_dev_register(dev, sunxi_cphy->extcon); + + + return 0; + +err_node_put: + of_node_put(child); + + return ret; +} + +static int sunxi_cadence_phy_probe(struct platform_device *pdev) +{ + struct sunxi_cadence_phy *sunxi_cphy; + struct device *dev = &pdev->dev; + int ret; + + sunxi_cphy = devm_kzalloc(dev, sizeof(*sunxi_cphy), GFP_KERNEL); + if (!sunxi_cphy) + return -ENOMEM; + + sunxi_cphy->dev = dev; + dev_set_drvdata(dev, sunxi_cphy); + + ret = sunxi_cadence_phy_parse_dt(pdev); + if (ret) + return -EINVAL; + + ret = sunxi_cadence_phy_serdes_init(sunxi_cphy); + if (ret) + return -EINVAL; + dev_info(dev, "Sub System Version v%lu.%lu.%lu\n", FIELD_GET(TOP_GEN_VER, sunxi_cphy->vernum), + FIELD_GET(TOP_SUB_VER, sunxi_cphy->vernum), FIELD_GET(TOP_PRJ_VER, sunxi_cphy->vernum)); + + return 0; +} + +static void sunxi_cadence_phy_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sunxi_cadence_phy *sunxi_cphy = dev_get_drvdata(dev); + + sunxi_cadence_phy_serdes_exit(sunxi_cphy); +} + +static int __maybe_unused sunxi_cadence_phy_suspend(struct device *dev) +{ + struct sunxi_cadence_phy *sunxi_cphy = dev_get_drvdata(dev); + + sunxi_cadence_phy_serdes_exit(sunxi_cphy); + + return 0; +} + +static int __maybe_unused sunxi_cadence_phy_resume(struct device *dev) +{ + struct sunxi_cadence_phy *sunxi_cphy = dev_get_drvdata(dev); + struct sunxi_cadence_combophy *combo0 = sunxi_cphy->combo0; + int ret; + u32 reg_val; + + ret = sunxi_cadence_phy_serdes_init(sunxi_cphy); + if (ret) { + dev_err(dev, "failed to resume sub system\n"); + return ret; + } + + reg_val = readl(COMB0_REG_PHYPINS_LINK_CTRL0(combo0->top_reg)); + if (combo0->orientation == TYPEC_ORIENTATION_REVERSE) + reg_val |= PHY0_TYPEC_CONN_DIR; + else + reg_val &= ~PHY0_TYPEC_CONN_DIR; + writel(reg_val, COMB0_REG_PHYPINS_LINK_CTRL0(combo0->top_reg)); + + + return 0; +} + +static struct dev_pm_ops sunxi_cadence_phy_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(sunxi_cadence_phy_suspend, sunxi_cadence_phy_resume) +}; + +static const struct of_device_id sunxi_cadence_phy_of_match_table[] = { + { + .compatible = "allwinner,cadence-combophy", + }, +}; + +static struct platform_driver sunxi_cadence_phy_driver = { + .probe = sunxi_cadence_phy_probe, + .remove = sunxi_cadence_phy_remove, + .driver = { + .name = "sunxi-cadence-combophy", + .pm = &sunxi_cadence_phy_pm_ops, + .of_match_table = sunxi_cadence_phy_of_match_table, + }, +}; +module_platform_driver(sunxi_cadence_phy_driver); + +MODULE_AUTHOR("huangyongxing@allwinnertech.com"); +MODULE_DESCRIPTION("Allwinner CADENCE COMBOPHY driver"); +MODULE_LICENSE("GPL v2"); From 6d5e48041ebff4339ddf3cf734ae097d2b0758ed Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Tue, 1 Sep 2026 21:40:58 +0000 Subject: [PATCH 2/9] phy: sunxi-candence: Correctly defer probing when missing 1v8-supply Signed-off-by: Yao Zi --- drivers/phy/allwinner/sunxi-cadence-combophy.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/phy/allwinner/sunxi-cadence-combophy.c b/drivers/phy/allwinner/sunxi-cadence-combophy.c index 70c3a6df9b8573..5314a5eda0160e 100644 --- a/drivers/phy/allwinner/sunxi-cadence-combophy.c +++ b/drivers/phy/allwinner/sunxi-cadence-combophy.c @@ -4201,7 +4201,8 @@ static int sunxi_cadence_phy_parse_dt(struct platform_device *pdev) /* serdes 1.8V AVDD-H-COMB0 supply */ sunxi_cphy->serdes1v8_supply = devm_regulator_get_optional(dev, "serdes1v8"); if (IS_ERR(sunxi_cphy->serdes1v8_supply)) - dev_err(dev, "get serdes 1v8-supply fail\n"); + return dev_err_probe(dev, PTR_ERR(sunxi_cphy->serdes1v8_supply), + "get serdes 1v8-supply fail\n"); /* sunxi_cphy->avdd_h_regulator = devm_regulator_get(dev, "avdd-h"); @@ -4292,7 +4293,7 @@ static int sunxi_cadence_phy_probe(struct platform_device *pdev) ret = sunxi_cadence_phy_parse_dt(pdev); if (ret) - return -EINVAL; + return ret; ret = sunxi_cadence_phy_serdes_init(sunxi_cphy); if (ret) From 0ec0db2fc833631d07e75e8686a2a304823ac7d7 Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Mon, 3 Aug 2026 20:47:59 +0000 Subject: [PATCH 3/9] PCI: allwinner: Introduce RC & EP driver Only the RC part has been tested. Signed-off-by: Yao Zi --- drivers/pci/controller/Kconfig | 1 + drivers/pci/controller/Makefile | 1 + drivers/pci/controller/allwinner/Kconfig | 29 + drivers/pci/controller/allwinner/Makefile | 6 + .../pci/controller/allwinner/pcie-sunxi-dma.c | 269 ++++ .../pci/controller/allwinner/pcie-sunxi-dma.h | 280 ++++ .../pci/controller/allwinner/pcie-sunxi-ep.c | 817 +++++++++++ .../controller/allwinner/pcie-sunxi-plat.c | 1244 +++++++++++++++++ .../pci/controller/allwinner/pcie-sunxi-rc.c | 830 +++++++++++ drivers/pci/controller/allwinner/pcie-sunxi.h | 401 ++++++ 10 files changed, 3878 insertions(+) create mode 100644 drivers/pci/controller/allwinner/Kconfig create mode 100644 drivers/pci/controller/allwinner/Makefile create mode 100644 drivers/pci/controller/allwinner/pcie-sunxi-dma.c create mode 100644 drivers/pci/controller/allwinner/pcie-sunxi-dma.h create mode 100644 drivers/pci/controller/allwinner/pcie-sunxi-ep.c create mode 100644 drivers/pci/controller/allwinner/pcie-sunxi-plat.c create mode 100644 drivers/pci/controller/allwinner/pcie-sunxi-rc.c create mode 100644 drivers/pci/controller/allwinner/pcie-sunxi.h diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig index 0452151a7bccc8..a68dccc61300cd 100644 --- a/drivers/pci/controller/Kconfig +++ b/drivers/pci/controller/Kconfig @@ -358,6 +358,7 @@ config PCIE_XILINX_CPM Say 'Y' here if you want kernel support for the Xilinx Versal CPM host bridge. +source "drivers/pci/controller/allwinner/Kconfig" source "drivers/pci/controller/cadence/Kconfig" source "drivers/pci/controller/dwc/Kconfig" source "drivers/pci/controller/mobiveil/Kconfig" diff --git a/drivers/pci/controller/Makefile b/drivers/pci/controller/Makefile index 038ccbd9e3ba23..2b95352d0e3cc0 100644 --- a/drivers/pci/controller/Makefile +++ b/drivers/pci/controller/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_PCIE_APPLE) += pcie-apple.o obj-$(CONFIG_PCIE_MT7621) += pcie-mt7621.o # pcie-hisi.o quirks are needed even without CONFIG_PCIE_DW +obj-y += allwinner/ obj-y += dwc/ obj-y += mobiveil/ obj-y += plda/ diff --git a/drivers/pci/controller/allwinner/Kconfig b/drivers/pci/controller/allwinner/Kconfig new file mode 100644 index 00000000000000..a443cc83ec1afe --- /dev/null +++ b/drivers/pci/controller/allwinner/Kconfig @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Copyright (C) 2015-2025 Allwinner Technology Co., Ltd. + +menu "Allwinner PCIe Drivers" + depends on PCI + +choice + prompt "Allwinner PCIe controller support" + default PCIE_SUNXI_NONE + +config PCIE_SUNXI_RC + bool "RC controller - Host mode" + help + Enables support for the PCIe RC controller in the Allwinner SoC. + +config PCIE_SUNXI_EP + bool "EP controller - Endpoint mode" + select PCI_ENDPOINT + help + Enables support for the PCIe EP controller in the Allwinner SoC. + +config PCIE_SUNXI_NONE + bool "None" + help + Disable support for the PCIe controller in the Allwinner SoC. + +endchoice + +endmenu diff --git a/drivers/pci/controller/allwinner/Makefile b/drivers/pci/controller/allwinner/Makefile new file mode 100644 index 00000000000000..d485e461f67d7d --- /dev/null +++ b/drivers/pci/controller/allwinner/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2015-2025 Allwinner Technology Co., Ltd. +pcie_sunxi_host-objs := pcie-sunxi-rc.o pcie-sunxi-dma.o pcie-sunxi-plat.o +pcie_sunxi_ep-objs := pcie-sunxi-ep.o pcie-sunxi-dma.o pcie-sunxi-plat.o +obj-$(CONFIG_PCIE_SUNXI_RC) += pcie_sunxi_host.o +obj-$(CONFIG_PCIE_SUNXI_EP) += pcie_sunxi_ep.o diff --git a/drivers/pci/controller/allwinner/pcie-sunxi-dma.c b/drivers/pci/controller/allwinner/pcie-sunxi-dma.c new file mode 100644 index 00000000000000..72630a4dc7d3d4 --- /dev/null +++ b/drivers/pci/controller/allwinner/pcie-sunxi-dma.c @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ +/* + * Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. + * + * The pcie_dma_chnl_request() is used to apply for pcie DMA channels; + * The pcie_dma_mem_xxx() is to initiate DMA read and write operations; + * + */ + +#define SUNXI_MODNAME "pcie-edma" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pcie-sunxi-dma.h" + + +static struct dma_trx_obj *obj_global; + +sunxi_pci_edma_chan_t *sunxi_pcie_dma_chan_request(enum dma_dir dma_trx, void *cb, void *data) +{ + struct sunxi_pcie *pci = dev_get_drvdata(obj_global->dev); + sunxi_pci_edma_chan_t *edma_chan = NULL; + u32 free_chan; + + if (dma_trx == PCIE_DMA_WRITE) { + free_chan = find_first_zero_bit(pci->wr_edma_map, pci->num_edma); + + if (free_chan >= pci->num_edma) { + dev_err(pci->dev, "No free pcie edma write channel.\n"); + return NULL; + } + + set_bit(free_chan, pci->wr_edma_map); + + edma_chan = &pci->dma_wr_chn[free_chan]; + + edma_chan->dma_trx = PCIE_DMA_WRITE; + edma_chan->chnl_num = free_chan; + edma_chan->callback = cb; + edma_chan->callback_param = data; + + return edma_chan; + } else if (dma_trx == PCIE_DMA_READ) { + free_chan = find_first_zero_bit(pci->rd_edma_map, pci->num_edma); + + if (free_chan >= pci->num_edma) { + dev_err(pci->dev, "No free pcie edma read channel.\n"); + return NULL; + } + + set_bit(free_chan, pci->rd_edma_map); + + edma_chan = &pci->dma_rd_chn[free_chan]; + + edma_chan->dma_trx = PCIE_DMA_READ; + edma_chan->chnl_num = free_chan; + edma_chan->callback = cb; + edma_chan->callback_param = data; + + return edma_chan; + } else { + dev_err(pci->dev, "ERR: unsupported type:%d \n", dma_trx); + } + + return NULL; +} +EXPORT_SYMBOL_GPL(sunxi_pcie_dma_chan_request); + +int sunxi_pcie_dma_chan_release(struct sunxi_pci_edma_chan *edma_chan, enum dma_dir dma_trx) +{ + struct sunxi_pcie *pci = dev_get_drvdata(obj_global->dev); + + if (edma_chan->chnl_num >= pci->num_edma) { + dev_err(pci->dev, "ERR: the channel num:%d is error\n", edma_chan->chnl_num); + return -1; + } + + if (PCIE_DMA_WRITE == dma_trx) { + edma_chan->callback = NULL; + edma_chan->callback_param = NULL; + clear_bit(edma_chan->chnl_num, pci->wr_edma_map); + } else if (PCIE_DMA_READ == dma_trx) { + edma_chan->callback = NULL; + edma_chan->callback_param = NULL; + clear_bit(edma_chan->chnl_num, pci->rd_edma_map); + } else { + dev_err(pci->dev, "ERR: unsupported type:%d \n", dma_trx); + } + + return 0; +} +EXPORT_SYMBOL_GPL(sunxi_pcie_dma_chan_release); + +static int sunxi_pcie_init_edma_map(struct sunxi_pcie *pci) +{ + pci->rd_edma_map = devm_bitmap_zalloc(pci->dev, pci->num_edma, GFP_KERNEL); + if (!pci->rd_edma_map) + return -ENOMEM; + + pci->wr_edma_map = devm_bitmap_zalloc(pci->dev, pci->num_edma, GFP_KERNEL); + if (!pci->wr_edma_map) + return -ENOMEM; + + return 0; +} + +int sunxi_pcie_dma_get_chan(struct platform_device *pdev) +{ + struct sunxi_pcie *pci = platform_get_drvdata(pdev); + sunxi_pci_edma_chan_t *edma_chan = NULL; + int ret, i; + + ret = of_property_read_u32(pdev->dev.of_node, "num-edma", &pci->num_edma); + if (ret) { + dev_err(&pdev->dev, "Failed to parse the number of edma\n"); + return -EINVAL; + } else { + ret = sunxi_pcie_init_edma_map(pci); + if (ret) + return -EINVAL; + } + + pci->dma_wr_chn = devm_kcalloc(&pdev->dev, pci->num_edma, sizeof(sunxi_pci_edma_chan_t), GFP_KERNEL); + pci->dma_rd_chn = devm_kcalloc(&pdev->dev, pci->num_edma, sizeof(sunxi_pci_edma_chan_t), GFP_KERNEL); + if (!pci->dma_wr_chn || !pci->dma_rd_chn) { + dev_err(&pdev->dev, "PCIe edma init failed\n"); + return -EINVAL; + } + + for (i = 0; i < pci->num_edma; i++) { + edma_chan = &pci->dma_wr_chn[i]; + spin_lock_init(&edma_chan->lock); + } + + for (i = 0; i < pci->num_edma; i++) { + edma_chan = &pci->dma_rd_chn[i]; + spin_lock_init(&edma_chan->lock); + } + + return 0; +} +EXPORT_SYMBOL_GPL(sunxi_pcie_dma_get_chan); + +int sunxi_pcie_edma_config_start(struct sunxi_pci_edma_chan *edma_chan) +{ + struct dma_table edma_table = {0}; + int ret; + + if (likely(obj_global->config_dma_trx_func)) { + ret = obj_global->config_dma_trx_func(&edma_table, edma_chan->src_addr, edma_chan->dst_addr, + edma_chan->size, edma_chan->dma_trx, edma_chan); + + if (ret < 0) { + dev_err(obj_global->dev, "pcie dma mem read error ! \n"); + return -EINVAL; + } + } else { + dev_err(obj_global->dev, "config_dma_trx_func is NULL ! \n"); + return -EINVAL; + } + + obj_global->start_dma_trx_func(&edma_table, obj_global); + + return 0; +} +EXPORT_SYMBOL_GPL(sunxi_pcie_edma_config_start); + +int sunxi_pcie_dma_mem_read(phys_addr_t src_addr, phys_addr_t dst_addr, unsigned int size, void *chan) +{ + struct dma_table read_table = {0}; + int ret; + + if (likely(obj_global->config_dma_trx_func)) { + ret = obj_global->config_dma_trx_func(&read_table, src_addr, dst_addr, size, PCIE_DMA_READ, chan); + + if (ret < 0) { + dev_err(obj_global->dev, "pcie dma mem read error ! \n"); + return -EINVAL; + } + } else { + dev_err(obj_global->dev, "config_dma_trx_func is NULL ! \n"); + return -EINVAL; + } + + obj_global->start_dma_trx_func(&read_table, obj_global); + + return 0; +} +EXPORT_SYMBOL_GPL(sunxi_pcie_dma_mem_read); + +int sunxi_pcie_dma_mem_write(phys_addr_t src_addr, phys_addr_t dst_addr, unsigned int size, void *chan) +{ + struct dma_table write_table = {0}; + int ret; + + if (likely(obj_global->config_dma_trx_func)) { + ret = obj_global->config_dma_trx_func(&write_table, src_addr, dst_addr, size, PCIE_DMA_WRITE, chan); + + if (ret < 0) { + dev_err(obj_global->dev, "pcie dma mem write error ! \n"); + return -EINVAL; + } + } else { + dev_err(obj_global->dev, "config_dma_trx_func is NULL ! \n"); + return -EINVAL; + } + + obj_global->start_dma_trx_func(&write_table, obj_global); + + return 0; +} +EXPORT_SYMBOL_GPL(sunxi_pcie_dma_mem_write); + +struct dma_trx_obj *sunxi_pcie_dma_obj_probe(struct device *dev) +{ + struct dma_trx_obj *obj; + + obj = devm_kzalloc(dev, sizeof(*obj), GFP_KERNEL); + if (!obj) + return ERR_PTR(-ENOMEM); + + obj_global = obj; + obj->dev = dev; + + INIT_LIST_HEAD(&obj->dma_list); + spin_lock_init(&obj->dma_list_lock); + + mutex_init(&obj->count_mutex); + + return obj; +} +EXPORT_SYMBOL_GPL(sunxi_pcie_dma_obj_probe); + +int sunxi_pcie_dma_obj_remove(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct sunxi_pcie *pci = platform_get_drvdata(pdev); + + memset(pci->dma_wr_chn, 0, sizeof(sunxi_pci_edma_chan_t) * pci->num_edma); + memset(pci->dma_rd_chn, 0, sizeof(sunxi_pci_edma_chan_t) * pci->num_edma); + + obj_global->dma_list.next = NULL; + obj_global->dma_list.prev = NULL; + mutex_destroy(&obj_global->count_mutex); + + obj_global = NULL; + + return 0; +} +EXPORT_SYMBOL_GPL(sunxi_pcie_dma_obj_remove); diff --git a/drivers/pci/controller/allwinner/pcie-sunxi-dma.h b/drivers/pci/controller/allwinner/pcie-sunxi-dma.h new file mode 100644 index 00000000000000..99f58e1899c6da --- /dev/null +++ b/drivers/pci/controller/allwinner/pcie-sunxi-dma.h @@ -0,0 +1,280 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ +/* + * allwinner PCIe dma driver + * + * Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. + * + * Author: songjundong + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef _PCIE_SUNXI_DMA_H +#define _PCIE_SUNXI_DMA_H + +#include +#include + +#include "pcie-sunxi.h" + +#define PCIE_DMA_TABLE_NUM 8 +#define PCIE_DMA_TRX_TYPE_NUM 3 + +#define PCIE_WEIGHT 0x1f +/* + * MASK_DONE_CNT_xx and MASK_ABORT_CNT_xx used in dma interrupt + */ +#define MASK_DONE_CNT_WR ((2 << (PCIE_DMA_WR_CHN_CNT - 1)) - 1) +#define MASK_DONE_CNT_RD ((2 << (PCIE_DMA_RD_CHN_CNT - 1)) - 1) + +#define MASK_ABORD_CNT_WR (((2 << (PCIE_DMA_WR_CHN_CNT - 1)) - 1)) +#define MASK_ABORD_CNT_RD (((2 << (PCIE_DMA_RD_CHN_CNT - 1)) - 1)) + +#define PCIE_DMA_OFFSET 0x380000 + +#define PCIE_DMA_WR_ENB 0xc +#define PCIE_DMA_WR_CTRL_LO 0x200 +#define PCIE_DMA_WR_CTRL_HI 0x204 +#define PCIE_DMA_WR_XFERSIZE 0x208 +#define PCIE_DMA_WR_SAR_LO 0x20c +#define PCIE_DMA_WR_SAR_HI 0x210 +#define PCIE_DMA_WR_DAR_LO 0x214 +#define PCIE_DMA_WR_DAR_HI 0x218 +#define PCIE_DMA_WR_WEILO 0x18 +#define PCIE_DMA_WR_WEIHI 0x1c +#define PCIE_DMA_WR_DOORBELL 0x10 +#define PCIE_DMA_WR_INT_STATUS 0x4c +#define PCIE_DMA_WR_INT_MASK 0x54 +#define PCIE_DMA_WR_INT_CLEAR 0x58 + +#define PCIE_DMA_RD_ENB 0x2c +#define PCIE_DMA_RD_CTRL_LO 0x300 +#define PCIE_DMA_RD_CTRL_HI 0x304 +#define PCIE_DMA_RD_XFERSIZE 0x308 +#define PCIE_DMA_RD_SAR_LO 0x30c +#define PCIE_DMA_RD_SAR_HI 0x310 +#define PCIE_DMA_RD_DAR_LO 0x314 +#define PCIE_DMA_RD_DAR_HI 0x318 +#define PCIE_DMA_RD_WEILO 0x38 +#define PCIE_DMA_RD_WEIHI 0x3c +#define PCIE_DMA_RD_DOORBELL 0x30 +#define PCIE_DMA_RD_INT_STATUS 0xa0 +#define PCIE_DMA_RD_INT_MASK 0xa8 +#define PCIE_DMA_RD_INT_CLEAR 0xac + +#define PCIE_DMA_INT_MASK 0xf000f + +enum dma_dir { + PCIE_DMA_WRITE = 0, + PCIE_DMA_READ, +}; + +typedef void (*sunxi_pcie_edma_callback)(void *param); + +typedef struct sunxi_pci_edma_chan { + u32 chnl_num; + spinlock_t lock; + bool cookie; + phys_addr_t src_addr; + phys_addr_t dst_addr; + u32 size; + enum dma_dir dma_trx; + void *callback_param; + sunxi_pcie_edma_callback callback; +} sunxi_pci_edma_chan_t; + +/* + * The Channel Control Register for read and write. + */ +union chan_ctrl_lo { + struct { + u32 cb :1; /* 0 bit */ + u32 tcb :1; /* 1 */ + u32 llp :1; /* 2 */ + u32 lie :1; /* 3 */ + u32 rie :1; /* 4 */ + u32 cs :2; /* 5:6 */ + u32 rsvd1 :1; /* 7 */ + u32 ccs :1; /* 8 */ + u32 llen :1; /* 9 */ + u32 b_64s :1; /* 10 */ + u32 b_64d :1; /* 11 */ + u32 fn :5; /* 12:16 */ + u32 rsvd2 :7; /* 17:23 */ + u32 ns :1; /* 24 */ + u32 ro :1; /* 25 */ + u32 td :1; /* 26 */ + u32 tc :3; /* 27:29 */ + u32 at :2; /* 30:31 */ + }; + u32 dword; +}; + +/* + * The Channel Control Register high part for read and write. + * Note: depend on CX_SRIOV_ENABLE + * Note: Need to confirm the difference between PCIe 2.0 with 3.0 + */ +union chan_ctrl_hi { + struct { + u32 vfenb :1; /* 0 bit */ + u32 vfunc :8; /* 1-8 */ + u32 rsvd0 :23; /* 9-31 */ + }; + u32 dword; +}; + +struct ctx_reg { + union chan_ctrl_lo ctrllo; + union chan_ctrl_hi ctrlhi; + u32 xfersize; + u32 sarptrlo; + u32 sarptrhi; + u32 darptrlo; + u32 darptrhi; +}; + +/* + * The Channel Weight Register for read and write. + * + * weight_lo->weight0 means set channel 0 + * weight_hi->weight0 means set channel 4; + * + * Example: + * write channel #0 weight to 32 + * write channel #1 weight to 16 + * + * Then the DMA will issue 32 MRd requests for #0,followed by 16 MRd requests for #1, + * followed by the 32 MRd requests for #0 and so on... + */ +union weight { + struct { + u32 weight0 :5; /* 0:4 bit */ + u32 weight1 :5; /* 5:9 */ + u32 weight2 :5; /* 10:14 */ + u32 weight3 :5; /* 15:19 */ + u32 rsvd :12; /* 20:31 */ + }; + u32 dword; +}; + + +/* + * The Doorbell Register for read and write. + * if is read db: you need write 0x0 for that channel + * if is write db: you need write channel number for that channel. + */ +union db { + struct { + u32 chnl :3; /* 0 bit */ + u32 rsvd :28; /* 3:30 */ + u32 stop :1; /* 31 */ + }; + u32 dword; +}; + +/* + * The Enable VIEWPORT Register for read and write. + */ +union enb { + struct { + u32 enb :1; /* 0 bit */ + u32 rsvd :31; /* 1:31 */ + }; + u32 dword; +}; + +/* + * The Interrupt Status Register for read and write. + */ +union int_status { + struct { + u32 done :8; /* 0:7 bit */ + u32 rsvd0 :8; /* 8:15 */ + u32 abort :8; /* 16:23 */ + u32 rsvd1 :8; /* 24:31 */ + }; + u32 dword; +}; + +/* + * The Interrupt Status Register for read and write. + */ +union int_clear { + struct { + u32 doneclr :8; /* 0:7 bit */ + u32 rsvd0 :8; /* 8:15 */ + u32 abortclr :8; /* 16:23 */ + u32 rsvd1 :8; /* 24:31 */ + }; + u32 dword; +}; + +/* + * The Context Registers for read and write. + */ +struct ctx_regs { + union chan_ctrl_lo ctrllo; + union chan_ctrl_hi ctrlhi; + u32 xfersize; + u32 sarptrlo; + u32 sarptrhi; + u32 darptrlo; + u32 darptrhi; +}; + +struct dma_table { + u32 *descs; + int chn; + phys_addr_t phys_descs; + enum dma_dir dir; + u32 type; + struct list_head dma_tbl; + union enb enb; + struct ctx_regs ctx_reg; + union weight weilo; + union weight weihi; + union db start; + phys_addr_t local; + phys_addr_t bus; + size_t size; +}; + +struct dma_trx_obj { + struct device *dev; + void *mem_base; + phys_addr_t mem_start; + size_t mem_size; + int dma_free; + spinlock_t dma_list_lock; /* lock dma table */ + struct list_head dma_list; + struct work_struct dma_trx_work; + wait_queue_head_t event_queue; + struct workqueue_struct *dma_trx_wq; + struct dma_table *table[PCIE_DMA_TABLE_NUM]; + struct task_struct *scan_thread; + struct hrtimer scan_timer; + void *priv; + struct completion done; + int ref_count; + struct mutex count_mutex; + unsigned long irq_num; + struct dentry *pcie_root; + struct pcie_misc_dev *pcie_dev; + void (*start_dma_trx_func)(struct dma_table *table, struct dma_trx_obj *obj); + int (*config_dma_trx_func)(struct dma_table *table, phys_addr_t sar_addr, phys_addr_t dar_addr, + unsigned int size, enum dma_dir dma_trx, sunxi_pci_edma_chan_t *edma_chn); +}; + +struct dma_trx_obj *sunxi_pcie_dma_obj_probe(struct device *dev); +int sunxi_pcie_dma_obj_remove(struct device *dev); +sunxi_pci_edma_chan_t *sunxi_pcie_dma_chan_request(enum dma_dir dma_trx, void *cb, void *data); +int sunxi_pcie_dma_chan_release(struct sunxi_pci_edma_chan *edma_chan, enum dma_dir dma_trx); +int sunxi_pcie_dma_mem_read(phys_addr_t sar_addr, phys_addr_t dar_addr, unsigned int size, void *chan); +int sunxi_pcie_dma_mem_write(phys_addr_t sar_addr, phys_addr_t dar_addr, unsigned int size, void *chan); +int sunxi_pcie_edma_config_start(struct sunxi_pci_edma_chan *edma_chan); +int sunxi_pcie_dma_get_chan(struct platform_device *pdev); + +#endif diff --git a/drivers/pci/controller/allwinner/pcie-sunxi-ep.c b/drivers/pci/controller/allwinner/pcie-sunxi-ep.c new file mode 100644 index 00000000000000..c91d41e37aab41 --- /dev/null +++ b/drivers/pci/controller/allwinner/pcie-sunxi-ep.c @@ -0,0 +1,817 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ +/* + * SUNXI PCIe Endpoint controller driver + * + * Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. + * Author: songjundong + */ + +#define SUNXI_MODNAME "pcie-ep" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../pci.h" +#include "pcie-sunxi.h" + +static void sunxi_pcie_setup_ep(struct sunxi_pcie *pci) +{ + sunxi_pcie_plat_set_rate(pci); +} + +static unsigned int sunxi_pcie_ep_func_select(struct sunxi_pcie_ep *ep, u8 func_no) +{ + unsigned int func_offset = 0; + + if (ep->ops->func_conf_select) + func_offset = ep->ops->func_conf_select(ep, func_no); + + return func_offset; +} + +static u8 __sunxi_pcie_ep_find_next_cap(struct sunxi_pcie_ep *ep, u8 func_no, + u8 cap_ptr, u8 cap) +{ + struct sunxi_pcie *pci = to_sunxi_pcie_from_ep(ep); + unsigned int func_offset = 0; + u8 cap_id, next_cap_ptr; + u16 reg; + + if (!cap_ptr) + return 0; + + func_offset = sunxi_pcie_ep_func_select(ep, func_no); + + reg = sunxi_pcie_readw_dbi(pci, func_offset + cap_ptr); + cap_id = (reg & 0x00ff); + + if (cap_id > PCI_CAP_ID_MAX) + return 0; + + if (cap_id == cap) + return cap_ptr; + + next_cap_ptr = (reg & 0xff00) >> 8; + return __sunxi_pcie_ep_find_next_cap(ep, func_no, next_cap_ptr, cap); +} + +static u8 sunxi_pcie_ep_find_capability(struct sunxi_pcie_ep *ep, u8 func_no, u8 cap) +{ + struct sunxi_pcie *pci = to_sunxi_pcie_from_ep(ep); + unsigned int func_offset = 0; + u8 next_cap_ptr; + u16 reg; + + func_offset = sunxi_pcie_ep_func_select(ep, func_no); + + reg = sunxi_pcie_readw_dbi(pci, func_offset + PCI_CAPABILITY_LIST); + next_cap_ptr = (reg & 0x00ff); + + return __sunxi_pcie_ep_find_next_cap(ep, func_no, next_cap_ptr, cap); +} + +static unsigned int sunxi_pcie_ep_find_ext_capability(struct sunxi_pcie *pci, int cap) +{ + u32 header; + int pos = PCI_CFG_SPACE_SIZE; + + while (pos) { + header = sunxi_pcie_readl_dbi(pci, pos); + if (PCI_EXT_CAP_ID(header) == cap) + return pos; + + pos = PCI_EXT_CAP_NEXT(header); + if (!pos) + break; + } + + return 0; +} + +struct sunxi_pcie_ep_func *sunxi_pcie_ep_get_func_from_ep(struct sunxi_pcie_ep *ep, u8 func_no) +{ + struct sunxi_pcie_ep_func *ep_func = NULL; + + list_for_each_entry(ep_func, &ep->func_list, list) { + if (ep_func->func_no == func_no) + return ep_func; + } + + return NULL; +} + +static void __sunxi_pcie_ep_reset_bar(struct sunxi_pcie *pci, u8 func_no, + enum pci_barno bar, int flags) +{ + u32 reg; + unsigned int func_offset = 0; + struct sunxi_pcie_ep *ep = &pci->ep; + + func_offset = sunxi_pcie_ep_func_select(ep, func_no); + + reg = func_offset + PCI_BASE_ADDRESS_0 + (4 * bar); + + sunxi_pcie_dbi_ro_wr_en(pci); + sunxi_pcie_writel_dbi(pci, reg, 0x0); + + if (flags & PCI_BASE_ADDRESS_MEM_TYPE_64) { + sunxi_pcie_writel_dbi(pci, reg + 4, 0x0); + } + sunxi_pcie_dbi_ro_wr_dis(pci); +} + +static void sunxi_pcie_ep_reset_bar(struct sunxi_pcie *pci, enum pci_barno bar) +{ + u8 func_no, funcs; + + funcs = pci->ep.epc->max_functions; + + for (func_no = 0; func_no < funcs; func_no++) + __sunxi_pcie_ep_reset_bar(pci, func_no, bar, 0); +} + +static void sunxi_pcie_prog_inbound_atu(struct sunxi_pcie *pci, u8 func_no, int index, + int type, u64 cpu_addr, u8 bar) +{ + sunxi_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET_INBOUND(index), + lower_32_bits(cpu_addr)); + sunxi_pcie_writel_dbi(pci, PCIE_ATU_UPPER_TARGET_INBOUND(index), + upper_32_bits(cpu_addr)); + sunxi_pcie_writel_dbi(pci, PCIE_ATU_CR1_INBOUND(index), + type | PCIE_ATU_FUNC_NUM(func_no)); + sunxi_pcie_writel_dbi(pci, PCIE_ATU_CR2_INBOUND(index), + PCIE_ATU_ENABLE | PCIE_ATU_FUNC_NUM_MATCH_EN | + PCIE_ATU_BAR_MODE_ENABLE | (bar << 8)); +} + +static void sunxi_pcie_prog_ep_outbound_atu(struct sunxi_pcie *pci, u8 func_no, int index, + int type, u64 cpu_addr, u64 pci_addr, + u64 size) +{ + sunxi_pcie_writel_dbi(pci, PCIE_ATU_LOWER_BASE_OUTBOUND(index), + lower_32_bits(cpu_addr)); + sunxi_pcie_writel_dbi(pci, PCIE_ATU_UPPER_BASE_OUTBOUND(index), + upper_32_bits(cpu_addr)); + sunxi_pcie_writel_dbi(pci, PCIE_ATU_LIMIT_OUTBOUND(index), + lower_32_bits(cpu_addr + size - 1)); + sunxi_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET_OUTBOUND(index), + lower_32_bits(pci_addr)); + sunxi_pcie_writel_dbi(pci, PCIE_ATU_UPPER_TARGET_OUTBOUND(index), upper_32_bits(pci_addr)); + sunxi_pcie_writel_dbi(pci, PCIE_ATU_CR1_OUTBOUND(index), + type | PCIE_ATU_FUNC_NUM(func_no)); + sunxi_pcie_writel_dbi(pci, PCIE_ATU_CR2_OUTBOUND(index), + PCIE_ATU_ENABLE); +} + +static int sunxi_pcie_ep_inbound_atu(struct sunxi_pcie_ep *ep, u8 func_no, int type, + dma_addr_t cpu_addr, enum pci_barno bar) +{ + u32 free_win; + struct sunxi_pcie *pci = to_sunxi_pcie_from_ep(ep); + + if (!ep->bar_to_atu[bar]) + free_win = find_first_zero_bit(ep->ib_window_map, ep->num_ib_windows); + else + free_win = ep->bar_to_atu[bar]; + + if (free_win >= ep->num_ib_windows) { + dev_err(pci->dev, "No free inbound window\n"); + return -EINVAL; + } + + sunxi_pcie_prog_inbound_atu(pci, func_no, free_win, type, + cpu_addr, bar); + + ep->bar_to_atu[bar] = free_win; + set_bit(free_win, ep->ib_window_map); + + return 0; +} + +static int sunxi_pcie_ep_outbound_atu(struct sunxi_pcie_ep *ep, u8 func_no, + phys_addr_t phys_addr, + u64 pci_addr, size_t size) +{ + struct sunxi_pcie *pci = to_sunxi_pcie_from_ep(ep); + u32 free_win; + + free_win = find_first_zero_bit(ep->ob_window_map, ep->num_ob_windows); + if (free_win >= ep->num_ob_windows) { + dev_err(pci->dev, "No free outbound window\n"); + return -EINVAL; + } + + sunxi_pcie_prog_ep_outbound_atu(pci, func_no, free_win, PCIE_ATU_TYPE_MEM, + phys_addr, pci_addr, size); + + set_bit(free_win, ep->ob_window_map); + ep->outbound_addr[free_win] = phys_addr; + + return 0; +} + +static int sunxi_pcie_find_index(struct sunxi_pcie_ep *ep, phys_addr_t addr, + u32 *atu_index) +{ + u32 index; + + for (index = 0; index < ep->num_ob_windows; index++) { + if (ep->outbound_addr[index] != addr) + continue; + *atu_index = index; + return 0; + } + + return -EINVAL; +} + +static void sunxi_ep_init_bar(struct sunxi_pcie_ep *ep) +{ + struct sunxi_pcie *pci = to_sunxi_pcie_from_ep(ep); + struct sunxi_pcie_ep_func *ep_func; + enum pci_barno bar; + + ep_func = sunxi_pcie_ep_get_func_from_ep(ep, 0); + if (!ep_func) + return; + + for (bar = 0; bar < PCI_STD_NUM_BARS; bar++) + sunxi_pcie_ep_reset_bar(pci, bar); +} + +static int sunxi_pcie_start_link(struct sunxi_pcie *pci) +{ + sunxi_pcie_plat_ltssm_enable(pci); + + return 0; +} + +static void sunxi_pcie_stop_link(struct sunxi_pcie *pci) +{ + sunxi_pcie_plat_ltssm_disable(pci); +} + +static const struct of_device_id sunxi_pcie_ep_of_match[] = { + { .compatible = "sunxi,sun55iw3-pcie-ep"}, + {}, +}; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0) +static int sunxi_pcie_ep_write_header(struct pci_epc *epc, u8 func_no, + struct pci_epf_header *hdr) +#else +static int sunxi_pcie_ep_write_header(struct pci_epc *epc, u8 func_no, u8 vfunc_no, + struct pci_epf_header *hdr) +#endif +{ + struct sunxi_pcie_ep *ep = epc_get_drvdata(epc); + struct sunxi_pcie *pci = to_sunxi_pcie_from_ep(ep); + unsigned int func_offset = 0; + + func_offset = sunxi_pcie_ep_func_select(ep, func_no); + + sunxi_pcie_dbi_ro_wr_en(pci); + sunxi_pcie_writew_dbi(pci, func_offset + PCI_VENDOR_ID, hdr->vendorid); + sunxi_pcie_writew_dbi(pci, func_offset + PCI_DEVICE_ID, hdr->deviceid); + sunxi_pcie_writeb_dbi(pci, func_offset + PCI_REVISION_ID, hdr->revid); + sunxi_pcie_writeb_dbi(pci, func_offset + PCI_CLASS_PROG, hdr->progif_code); + sunxi_pcie_writew_dbi(pci, func_offset + PCI_CLASS_DEVICE, + hdr->subclass_code | hdr->baseclass_code << 8); + sunxi_pcie_writeb_dbi(pci, func_offset + PCI_CACHE_LINE_SIZE, + hdr->cache_line_size); + sunxi_pcie_writew_dbi(pci, func_offset + PCI_SUBSYSTEM_VENDOR_ID, + hdr->subsys_vendor_id); + sunxi_pcie_writew_dbi(pci, func_offset + PCI_SUBSYSTEM_ID, hdr->subsys_id); + sunxi_pcie_writeb_dbi(pci, func_offset + PCI_INTERRUPT_PIN, + hdr->interrupt_pin); + sunxi_pcie_dbi_ro_wr_dis(pci); + + return 0; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0) +static int sunxi_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, + struct pci_epf_bar *epf_bar) +#else +static int sunxi_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no, + struct pci_epf_bar *epf_bar) +#endif +{ + struct sunxi_pcie_ep *ep = epc_get_drvdata(epc); + struct sunxi_pcie *pci = to_sunxi_pcie_from_ep(ep); + enum pci_barno bar = epf_bar->barno; + size_t size = epf_bar->size; + int flags = epf_bar->flags; + unsigned int func_offset = 0; + unsigned int offset; + int ret, type, value; + u32 reg, dbi2_reg; + + func_offset = sunxi_pcie_ep_func_select(ep, func_no); + + reg = PCI_BASE_ADDRESS_0 + (4 * bar) + func_offset; + dbi2_reg = PCI_BASE_ADDRESS_0 + (4 * bar) + func_no * DBI2_FUNC_OFFSET; + + if (!(flags & PCI_BASE_ADDRESS_SPACE)) { + type = PCIE_ATU_TYPE_MEM; + } else { + type = PCIE_ATU_TYPE_IO; + dev_info(pci->dev, "IO type is not currently supported."); + return -1; + } + + ret = sunxi_pcie_ep_inbound_atu(ep, func_no, type, epf_bar->phys_addr, bar); + if (ret) + return ret; + + if (ep->epf_bar[bar]) + return 0; + + sunxi_pcie_dbi_ro_wr_en(pci); + + sunxi_pcie_writel_dbi(pci, PCIE_TYPE0_STATUS_COMMAND_REG, 0x6); + + offset = sunxi_pcie_ep_find_ext_capability(pci, PCI_EXT_CAP_ID_REBAR); + offset += func_offset; + + value = fls64(size - 1); + if (value <= SIZE_OF_1MB) { + value = 0; + dev_info(pci->dev, "The min size default is 1MB, set 1MB for func%d bar%d\n", + func_no, bar); + } else { + value -= SIZE_OF_1MB; + dev_info(pci->dev, "Only supports power-of-2 MB alignment, so func%d bar%d is set to %dMB", + func_no, bar, 1 << value); + } + + value = (value << PCI_REBAR_CTRL_BAR_SHIFT) & PCI_REBAR_CTRL_BAR_SIZE; + + if (flags & PCI_BASE_ADDRESS_MEM_TYPE_64) { + sunxi_pcie_writel_dbi(pci, reg, PCI_BASE_ADDRESS_MEM_TYPE_64); + if (bar == 0) { + sunxi_pcie_writel_dbi(pci, offset + RESBAR_CAP_REG, RESBAR_SIZE_MASK); + sunxi_pcie_writel_dbi(pci, offset + RESBAR_CTL_REG, value); + } else if (bar == 4) { + sunxi_pcie_writel_dbi(pci, offset + RESBAR_CAP_REG + (3 * RESBAR_NEXT_BAR), RESBAR_SIZE_MASK); + sunxi_pcie_writel_dbi(pci, offset + RESBAR_CTL_REG + (3 * RESBAR_NEXT_BAR), value); + } else { + dev_info(pci->dev, "No free 64bit bar."); + } + } else { + /* 32bit and mem space mode. */ + sunxi_pcie_writel_dbi(pci, reg, 0x0); + if (bar == 2 || bar == 3) { + bar -= 1; + sunxi_pcie_writel_dbi(pci, offset + RESBAR_CAP_REG + (bar * RESBAR_NEXT_BAR), RESBAR_SIZE_MASK); + sunxi_pcie_writel_dbi(pci, offset + RESBAR_CTL_REG + (bar * RESBAR_NEXT_BAR), value); + } + } + + sunxi_pcie_writel_dbi(pci, PCIE_DBI2_BASE + dbi2_reg, BAR_ENABLE); + + ep->epf_bar[bar] = epf_bar; + sunxi_pcie_dbi_ro_wr_dis(pci); + + return 0; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0) +static void sunxi_pcie_ep_clear_bar(struct pci_epc *epc, u8 func_no, + struct pci_epf_bar *epf_bar) +#else +static void sunxi_pcie_ep_clear_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no, + struct pci_epf_bar *epf_bar) +#endif +{ + struct sunxi_pcie_ep *ep = epc_get_drvdata(epc); + struct sunxi_pcie *pci = to_sunxi_pcie_from_ep(ep); + enum pci_barno bar = epf_bar->barno; + u32 index = ep->bar_to_atu[bar]; + + __sunxi_pcie_ep_reset_bar(pci, func_no, bar, epf_bar->flags); + + sunxi_pcie_writel_dbi(pci, PCIE_ATU_CR2_INBOUND(index), 0); + clear_bit(index, ep->ib_window_map); + ep->epf_bar[bar] = NULL; + ep->bar_to_atu[bar] = 0; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0) +static void sunxi_pcie_ep_unmap_addr(struct pci_epc *epc, u8 func_no, + phys_addr_t addr) +#else +static void sunxi_pcie_ep_unmap_addr(struct pci_epc *epc, u8 func_no, u8 vfunc_no, + phys_addr_t addr) +#endif +{ + int ret; + u32 atu_index; + struct sunxi_pcie_ep *ep = epc_get_drvdata(epc); + struct sunxi_pcie *pci = to_sunxi_pcie_from_ep(ep); + + ret = sunxi_pcie_find_index(ep, addr, &atu_index); + if (ret < 0) + return; + + sunxi_pcie_writel_dbi(pci, PCIE_ATU_CR2_OUTBOUND(atu_index), 0); + clear_bit(atu_index, ep->ob_window_map); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0) +static int sunxi_pcie_ep_map_addr(struct pci_epc *epc, u8 func_no, + phys_addr_t cpu_addr, u64 pci_addr, size_t size) +#else +static int sunxi_pcie_ep_map_addr(struct pci_epc *epc, u8 func_no, u8 vfunc_no, + phys_addr_t cpu_addr, u64 pci_addr, size_t size) +#endif +{ + int ret; + struct sunxi_pcie_ep *ep = epc_get_drvdata(epc); + struct sunxi_pcie *pci = to_sunxi_pcie_from_ep(ep); + + ret = sunxi_pcie_ep_outbound_atu(ep, func_no, cpu_addr, pci_addr, size); + if (ret) { + dev_err(pci->dev, "Failed to enable address\n"); + return ret; + } + + return 0; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0) +static int sunxi_pcie_ep_set_msi(struct pci_epc *epc, u8 func_no, + u8 interrupts) +#else +static int sunxi_pcie_ep_set_msi(struct pci_epc *epc, u8 func_no, u8 vfunc_no, + u8 interrupts) +#endif +{ + struct sunxi_pcie_ep *ep = epc_get_drvdata(epc); + struct sunxi_pcie *pci = to_sunxi_pcie_from_ep(ep); + u32 val, reg; + unsigned int func_offset = 0; + struct sunxi_pcie_ep_func *ep_func; + + ep_func = sunxi_pcie_ep_get_func_from_ep(ep, func_no); + if (!ep_func || !ep_func->msi_cap) + return -EINVAL; + + func_offset = sunxi_pcie_ep_func_select(ep, func_no); + + reg = ep_func->msi_cap + func_offset + PCI_MSI_FLAGS; + val = sunxi_pcie_readw_dbi(pci, reg); + val &= ~PCI_MSI_FLAGS_QMASK; + val |= (interrupts << 1) & PCI_MSI_FLAGS_QMASK; + sunxi_pcie_dbi_ro_wr_en(pci); + sunxi_pcie_writew_dbi(pci, reg, val); + sunxi_pcie_dbi_ro_wr_dis(pci); + + return 0; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0) +static int sunxi_pcie_ep_get_msi(struct pci_epc *epc, u8 func_no) +#else +static int sunxi_pcie_ep_get_msi(struct pci_epc *epc, u8 func_no, u8 vfunc_no) +#endif +{ + struct sunxi_pcie_ep *ep = epc_get_drvdata(epc); + struct sunxi_pcie *pci = to_sunxi_pcie_from_ep(ep); + u32 val, reg; + unsigned int func_offset = 0; + struct sunxi_pcie_ep_func *ep_func; + + ep_func = sunxi_pcie_ep_get_func_from_ep(ep, func_no); + if (!ep_func || !ep_func->msi_cap) + return -EINVAL; + + func_offset = sunxi_pcie_ep_func_select(ep, func_no); + + reg = ep_func->msi_cap + func_offset + PCI_MSI_FLAGS; + val = sunxi_pcie_readw_dbi(pci, reg); + if (!(val & PCI_MSI_FLAGS_ENABLE)) + return -EINVAL; + + val = (val & PCI_MSI_FLAGS_QSIZE) >> 4; + + return val; +} + +static int sunxi_pcie_ep_send_msi_irq(struct sunxi_pcie_ep *ep, u8 func_no, + u8 interrupt_num) +{ + struct sunxi_pcie *pci = to_sunxi_pcie_from_ep(ep); + struct sunxi_pcie_ep_func *ep_func; + struct pci_epc *epc = ep->epc; + unsigned int aligned_offset; + unsigned int func_offset = 0; + u16 msg_ctrl, msg_data; + u32 msg_addr_lower, msg_addr_upper, reg; + u64 msg_addr; + bool has_upper; + int ret; + + ep_func = sunxi_pcie_ep_get_func_from_ep(ep, func_no); + if (!ep_func || !ep_func->msi_cap) + return -EINVAL; + + func_offset = sunxi_pcie_ep_func_select(ep, func_no); + + /* Raise MSI per the PCI Local Bus Specification Revision 3.0, 6.8.1. */ + reg = ep_func->msi_cap + func_offset + PCI_MSI_FLAGS; + msg_ctrl = sunxi_pcie_readw_dbi(pci, reg); + has_upper = !!(msg_ctrl & PCI_MSI_FLAGS_64BIT); + reg = ep_func->msi_cap + func_offset + PCI_MSI_ADDRESS_LO; + msg_addr_lower = sunxi_pcie_readl_dbi(pci, reg); + if (has_upper) { + reg = ep_func->msi_cap + func_offset + PCI_MSI_ADDRESS_HI; + msg_addr_upper = sunxi_pcie_readl_dbi(pci, reg); + reg = ep_func->msi_cap + func_offset + PCI_MSI_DATA_64; + msg_data = sunxi_pcie_readw_dbi(pci, reg); + } else { + msg_addr_upper = 0; + reg = ep_func->msi_cap + func_offset + PCI_MSI_DATA_32; + msg_data = sunxi_pcie_readw_dbi(pci, reg); + } + aligned_offset = msg_addr_lower & (epc->mem->window.page_size - 1); + msg_addr = ((u64)msg_addr_upper) << 32 | + (msg_addr_lower & ~aligned_offset); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0) + ret = sunxi_pcie_ep_map_addr(epc, func_no, ep->msi_mem_phys, msg_addr, + epc->mem->window.page_size); +#else + ret = sunxi_pcie_ep_map_addr(epc, func_no, 0, ep->msi_mem_phys, msg_addr, + epc->mem->window.page_size); +#endif + if (ret) + return ret; + + writel(msg_data | (interrupt_num - 1), ep->msi_mem + aligned_offset); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0) + sunxi_pcie_ep_unmap_addr(epc, func_no, ep->msi_mem_phys); +#else + sunxi_pcie_ep_unmap_addr(epc, func_no, 0, ep->msi_mem_phys); +#endif + return 0; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0) +static int sunxi_pcie_ep_raise_irq(struct pci_epc *epc, u8 fn, + enum pci_epc_irq_type type, u16 interrupt_num) +#elif (KERNEL_VERSION(5, 15, 0) <= LINUX_VERSION_CODE) && (LINUX_VERSION_CODE < KERNEL_VERSION(6, 12, 0)) +static int sunxi_pcie_ep_raise_irq(struct pci_epc *epc, u8 fn, u8 vfn, + enum pci_epc_irq_type type, u16 interrupt_num) +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0) +static int sunxi_pcie_ep_raise_irq(struct pci_epc *epc, u8 fn, u8 vfn, + unsigned int type, u16 interrupt_num) +#endif +{ + struct sunxi_pcie_ep *ep = epc_get_drvdata(epc); + + switch (type) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 12, 0) + case PCI_EPC_IRQ_MSI: +#else + case PCI_IRQ_MSI: +#endif + return sunxi_pcie_ep_send_msi_irq(ep, fn, interrupt_num); + default: + return -EINVAL; + } +} + +static int sunxi_pcie_ep_start(struct pci_epc *epc) +{ + struct sunxi_pcie_ep *ep = epc_get_drvdata(epc); + struct sunxi_pcie *pci = to_sunxi_pcie_from_ep(ep); + + sunxi_pcie_start_link(pci); + + return 0; +} + +static void sunxi_pcie_ep_stop(struct pci_epc *epc) +{ + struct sunxi_pcie_ep *ep = epc_get_drvdata(epc); + struct sunxi_pcie *pci = to_sunxi_pcie_from_ep(ep); + + sunxi_pcie_stop_link(pci); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 12, 0) +static const struct pci_epc_features sunxi_pcie_epc_features = { + .linkup_notifier = false, + .msi_capable = true, + .msix_capable = false, + .reserved_bar = BIT(BAR_2), + .bar_fixed_64bit = BIT(BAR_0) | BIT(BAR_4), + .align = SZ_1M, +}; +#else +static const struct pci_epc_features sunxi_pcie_epc_features = { + .linkup_notifier = false, + .msi_capable = true, + .msix_capable = false, + .bar[BAR_0] = { .only_64bit = true, }, + .bar[BAR_2] = { .type = BAR_RESERVED, }, + .bar[BAR_4] = { .only_64bit = true, }, + .align = SZ_1M, +}; +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0) +static const struct pci_epc_features *sunxi_pcie_ep_get_features(struct pci_epc *epc, u8 func_no) +#else +static const struct pci_epc_features *sunxi_pcie_ep_get_features(struct pci_epc *epc, u8 func_no, u8 vfunc_no) +#endif +{ + return &sunxi_pcie_epc_features; +} + + +static const struct pci_epc_ops sunxi_pcie_epc_ops = { + .write_header = sunxi_pcie_ep_write_header, + .set_bar = sunxi_pcie_ep_set_bar, + .clear_bar = sunxi_pcie_ep_clear_bar, + .map_addr = sunxi_pcie_ep_map_addr, + .unmap_addr = sunxi_pcie_ep_unmap_addr, + .set_msi = sunxi_pcie_ep_set_msi, + .get_msi = sunxi_pcie_ep_get_msi, + .raise_irq = sunxi_pcie_ep_raise_irq, + .start = sunxi_pcie_ep_start, + .stop = sunxi_pcie_ep_stop, + .get_features = sunxi_pcie_ep_get_features, +}; + +static int sunxi_pcie_parse_ep_dts(struct sunxi_pcie_ep *ep) +{ + int ret; + void *addr; + struct resource *res; + struct sunxi_pcie *pci = to_sunxi_pcie_from_ep(ep); + struct device *dev = pci->dev; + struct platform_device *pdev = to_platform_device(dev); + struct device_node *np = dev->of_node; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "addr_space"); + if (!res) { + dev_err(dev, "unable to read *addr_space* property\n"); + return -EINVAL; + } + + ep->phys_base = res->start; + ep->addr_size = resource_size(res); + ep->page_size = SZ_4K; + + ret = of_property_read_u32(np, "num-ib-windows", + &ep->num_ib_windows); + if (ret < 0) { + dev_err(dev, "unable to read *num-ib-windows* property\n"); + return ret; + } + + ret = of_property_read_u32(np, "num-ob-windows", + &ep->num_ob_windows); + if (ret < 0) { + dev_err(dev, "unable to read *num-ob-windows* property\n"); + return ret; + } + + ep->ib_window_map = devm_bitmap_zalloc(dev, ep->num_ib_windows, + GFP_KERNEL); + if (!ep->ib_window_map) + return -ENOMEM; + + ep->ob_window_map = devm_bitmap_zalloc(dev, ep->num_ob_windows, + GFP_KERNEL); + if (!ep->ob_window_map) + return -ENOMEM; + + addr = devm_kcalloc(dev, ep->num_ob_windows, sizeof(phys_addr_t), + GFP_KERNEL); + if (!addr) + return -ENOMEM; + ep->outbound_addr = addr; + + return 0; +} + + + +int sunxi_plat_ep_init_end(struct sunxi_pcie_ep *ep) +{ + struct sunxi_pcie *pci = to_sunxi_pcie_from_ep(ep); + u8 hdr_type; + + hdr_type = sunxi_pcie_readb_dbi(pci, PCI_HEADER_TYPE) & + PCI_HEADER_TYPE_MASK; + if (hdr_type != PCI_HEADER_TYPE_NORMAL) { + dev_err(pci->dev, + "PCIe controller is not set to EP mode (hdr_type:0x%x)!\n", + hdr_type); + return -EIO; + } + + sunxi_pcie_setup_ep(pci); + sunxi_pcie_dbi_ro_wr_dis(pci); + + return 0; +} + +int sunxi_pcie_ep_init(struct sunxi_pcie *pci) +{ + int ret; + u8 func_no; + struct pci_epc *epc; + struct sunxi_pcie_ep *ep = &pci->ep; + struct device *dev = pci->dev; + struct device_node *np = dev->of_node; + struct sunxi_pcie_ep_func *ep_func; + + INIT_LIST_HEAD(&ep->func_list); + + ret = sunxi_pcie_parse_ep_dts(ep); + if (ret) { + dev_err(dev, "failed to parse ep dts\n"); + return ret; + } + + epc = devm_pci_epc_create(dev, &sunxi_pcie_epc_ops); + if (IS_ERR(epc)) { + dev_err(dev, "failed to create epc device\n"); + return PTR_ERR(epc); + } + + ep->epc = epc; + epc_set_drvdata(epc, ep); + + ret = of_property_read_u8(np, "max-functions", &epc->max_functions); + if (ret < 0) + epc->max_functions = 1; + + for (func_no = 0; func_no < epc->max_functions; func_no++) { + ep_func = devm_kzalloc(dev, sizeof(*ep_func), GFP_KERNEL); + if (!ep_func) + return -ENOMEM; + + ep_func->func_no = func_no; + ep_func->msi_cap = sunxi_pcie_ep_find_capability(ep, func_no, + PCI_CAP_ID_MSI); + ep_func->msix_cap = sunxi_pcie_ep_find_capability(ep, func_no, + PCI_CAP_ID_MSIX); + + list_add_tail(&ep_func->list, &ep->func_list); + } + + sunxi_ep_init_bar(ep); + + ret = pci_epc_mem_init(epc, ep->phys_base, ep->addr_size, + ep->page_size); + if (ret < 0) { + dev_err(dev, "Failed to initialize address space\n"); + return ret; + } + + ep->msi_mem = pci_epc_mem_alloc_addr(epc, &ep->msi_mem_phys, + epc->mem->window.page_size); + if (!ep->msi_mem) { + ret = -ENOMEM; + dev_err(dev, "Failed to reserve memory for MSI\n"); + goto err_exit_epc_mem; + } + + ret = sunxi_plat_ep_init_end(ep); + if (ret) + goto err_free_epc_mem; + + return 0; + +err_free_epc_mem: + pci_epc_mem_free_addr(epc, ep->msi_mem_phys, ep->msi_mem, epc->mem->window.page_size); + +err_exit_epc_mem: + pci_epc_mem_exit(epc); + + return ret; +} +EXPORT_SYMBOL_GPL(sunxi_pcie_ep_init); + +void sunxi_pcie_ep_deinit(struct sunxi_pcie *pci) +{ + struct pci_epc *epc = pci->ep.epc; + struct sunxi_pcie_ep *ep = &pci->ep; + + pci_epc_mem_exit(epc); + pci_epc_mem_free_addr(epc, ep->msi_mem_phys, ep->msi_mem, epc->mem->window.page_size); +} +EXPORT_SYMBOL_GPL(sunxi_pcie_ep_deinit); diff --git a/drivers/pci/controller/allwinner/pcie-sunxi-plat.c b/drivers/pci/controller/allwinner/pcie-sunxi-plat.c new file mode 100644 index 00000000000000..14a4c1f118cb6f --- /dev/null +++ b/drivers/pci/controller/allwinner/pcie-sunxi-plat.c @@ -0,0 +1,1244 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ +/* + * PCIe driver for Allwinner Soc + * + * Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. + * + * Author: songjundong + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#define SUNXI_MODNAME "pcie" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../pci.h" +#include "pcie-sunxi-dma.h" +#include "pcie-sunxi.h" + +#define SUNXI_PCIE_MODULE_VERSION "1.2.5" + +void sunxi_pcie_writel(u32 val, struct sunxi_pcie *pcie, u32 offset) +{ + writel(val, pcie->app_base + offset); +} + +u32 sunxi_pcie_readl(struct sunxi_pcie *pcie, u32 offset) +{ + return readl(pcie->app_base + offset); +} + +void sunxi_pcie_writel_dbi(struct sunxi_pcie *pci, u32 reg, u32 val) +{ + sunxi_pcie_write_dbi(pci, reg, 0x4, val); +} + +u32 sunxi_pcie_readl_dbi(struct sunxi_pcie *pci, u32 reg) +{ + return sunxi_pcie_read_dbi(pci, reg, 0x4); +} + +void sunxi_pcie_writew_dbi(struct sunxi_pcie *pci, u32 reg, u16 val) +{ + sunxi_pcie_write_dbi(pci, reg, 0x2, val); +} + +u16 sunxi_pcie_readw_dbi(struct sunxi_pcie *pci, u32 reg) +{ + return sunxi_pcie_read_dbi(pci, reg, 0x2); +} + +void sunxi_pcie_writeb_dbi(struct sunxi_pcie *pci, u32 reg, u8 val) +{ + sunxi_pcie_write_dbi(pci, reg, 0x1, val); +} + +u8 sunxi_pcie_readb_dbi(struct sunxi_pcie *pci, u32 reg) +{ + return sunxi_pcie_read_dbi(pci, reg, 0x1); +} + +void sunxi_pcie_dbi_ro_wr_en(struct sunxi_pcie *pci) +{ + u32 val; + + val = sunxi_pcie_readl_dbi(pci, PCIE_MISC_CONTROL_1_CFG); + val |= (0x1 << 0); + sunxi_pcie_writel_dbi(pci, PCIE_MISC_CONTROL_1_CFG, val); +} + +void sunxi_pcie_dbi_ro_wr_dis(struct sunxi_pcie *pci) +{ + u32 val; + + val = sunxi_pcie_readl_dbi(pci, PCIE_MISC_CONTROL_1_CFG); + val &= ~(0x1 << 0); + sunxi_pcie_writel_dbi(pci, PCIE_MISC_CONTROL_1_CFG, val); +} + +static void sunxi_pcie_plat_set_mode(struct sunxi_pcie *pci) +{ + u32 val; + + switch (pci->drvdata->mode) { + case SUNXI_PCIE_EP_TYPE: + val = sunxi_pcie_readl(pci, PCIE_LTSSM_CTRL); + val &= ~DEVICE_TYPE_MASK; + sunxi_pcie_writel(val, pci, PCIE_LTSSM_CTRL); + break; + case SUNXI_PCIE_RC_TYPE: + val = sunxi_pcie_readl(pci, PCIE_LTSSM_CTRL); + val |= DEVICE_TYPE_RC; + sunxi_pcie_writel(val, pci, PCIE_LTSSM_CTRL); + break; + default: + dev_err(pci->dev, "unsupported device type:%d\n", pci->drvdata->mode); + break; + } +} + +static u8 __sunxi_pcie_find_next_cap(struct sunxi_pcie *pci, u8 cap_ptr, + u8 cap) +{ + u8 cap_id, next_cap_ptr; + u16 reg; + + if (!cap_ptr) + return 0; + + reg = sunxi_pcie_readw_dbi(pci, cap_ptr); + cap_id = (reg & CAP_ID_MASK); + + if (cap_id > PCI_CAP_ID_MAX) + return 0; + + if (cap_id == cap) + return cap_ptr; + + next_cap_ptr = (reg & NEXT_CAP_PTR_MASK) >> 8; + return __sunxi_pcie_find_next_cap(pci, next_cap_ptr, cap); +} + +static u8 sunxi_pcie_plat_find_capability(struct sunxi_pcie *pci, u8 cap) +{ + u8 next_cap_ptr; + u16 reg; + + reg = sunxi_pcie_readw_dbi(pci, PCI_CAPABILITY_LIST); + next_cap_ptr = (reg & CAP_ID_MASK); + + return __sunxi_pcie_find_next_cap(pci, next_cap_ptr, cap); +} + +int sunxi_pcie_cfg_read(void __iomem *addr, int size, u32 *val) +{ + if ((uintptr_t)addr & (size - 1)) { + *val = 0; + return PCIBIOS_BAD_REGISTER_NUMBER; + } + + if (size == 4) { + *val = readl(addr); + } else if (size == 2) { + *val = readw(addr); + } else if (size == 1) { + *val = readb(addr); + } else { + *val = 0; + return PCIBIOS_BAD_REGISTER_NUMBER; + } + + return PCIBIOS_SUCCESSFUL; +} +EXPORT_SYMBOL_GPL(sunxi_pcie_cfg_read); + +int sunxi_pcie_cfg_write(void __iomem *addr, int size, u32 val) +{ + if ((uintptr_t)addr & (size - 1)) + return PCIBIOS_BAD_REGISTER_NUMBER; + + if (size == 4) + writel(val, addr); + else if (size == 2) + writew(val, addr); + else if (size == 1) + writeb(val, addr); + else + return PCIBIOS_BAD_REGISTER_NUMBER; + + return PCIBIOS_SUCCESSFUL; +} +EXPORT_SYMBOL_GPL(sunxi_pcie_cfg_write); + +void sunxi_pcie_write_dbi(struct sunxi_pcie *pci, u32 reg, size_t size, u32 val) +{ + int ret; + + ret = sunxi_pcie_cfg_write(pci->dbi_base + reg, size, val); + if (ret) + dev_err(pci->dev, "Write DBI address failed\n"); +} +EXPORT_SYMBOL_GPL(sunxi_pcie_write_dbi); + +u32 sunxi_pcie_read_dbi(struct sunxi_pcie *pci, u32 reg, size_t size) +{ + int ret; + u32 val; + + ret = sunxi_pcie_cfg_read(pci->dbi_base + reg, size, &val); + if (ret) + dev_err(pci->dev, "Read DBI address failed\n"); + + return val; +} +EXPORT_SYMBOL_GPL(sunxi_pcie_read_dbi); + +static void sunxi_pcie_plat_set_link_cap(struct sunxi_pcie *pci, u32 link_gen) +{ + u32 cap, ctrl2, link_speed; + + u8 offset = sunxi_pcie_plat_find_capability(pci, PCI_CAP_ID_EXP); + + cap = sunxi_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCAP); + ctrl2 = sunxi_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCTL2); + ctrl2 &= ~PCI_EXP_LNKCTL2_TLS; + + switch (pcie_link_speed[link_gen]) { + case PCIE_SPEED_2_5GT: + link_speed = PCI_EXP_LNKCTL2_TLS_2_5GT; + break; + case PCIE_SPEED_5_0GT: + link_speed = PCI_EXP_LNKCTL2_TLS_5_0GT; + break; + case PCIE_SPEED_8_0GT: + link_speed = PCI_EXP_LNKCTL2_TLS_8_0GT; + break; + case PCIE_SPEED_16_0GT: + link_speed = PCI_EXP_LNKCTL2_TLS_16_0GT; + break; + default: + /* Use hardware capability */ + link_speed = FIELD_GET(PCI_EXP_LNKCAP_SLS, cap); + ctrl2 &= ~PCI_EXP_LNKCTL2_HASD; + break; + } + + sunxi_pcie_writel_dbi(pci, offset + PCI_EXP_LNKCTL2, ctrl2 | link_speed); + + cap &= ~((u32)PCI_EXP_LNKCAP_SLS); + sunxi_pcie_writel_dbi(pci, offset + PCI_EXP_LNKCAP, cap | link_speed); +} + +void sunxi_pcie_plat_set_rate(struct sunxi_pcie *pci) +{ + u32 val; + + sunxi_pcie_plat_set_link_cap(pci, pci->link_gen); + /* set the number of lanes */ + val = sunxi_pcie_readl_dbi(pci, PCIE_PORT_LINK_CONTROL); + val &= ~PORT_LINK_MODE_MASK; + switch (pci->lanes) { + case 1: + val |= PORT_LINK_MODE_1_LANES; + break; + case 2: + val |= PORT_LINK_MODE_2_LANES; + break; + case 4: + val |= PORT_LINK_MODE_4_LANES; + break; + default: + dev_err(pci->dev, "num-lanes %u: invalid value\n", pci->lanes); + return; + } + sunxi_pcie_writel_dbi(pci, PCIE_PORT_LINK_CONTROL, val); + + /* set link width speed control register */ + val = sunxi_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL); + val &= ~PORT_LOGIC_LINK_WIDTH_MASK; + switch (pci->lanes) { + case 1: + val |= PORT_LOGIC_LINK_WIDTH_1_LANES; + break; + case 2: + val |= PORT_LOGIC_LINK_WIDTH_2_LANES; + break; + case 4: + val |= PORT_LOGIC_LINK_WIDTH_4_LANES; + break; + } + sunxi_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val); +} +EXPORT_SYMBOL_GPL(sunxi_pcie_plat_set_rate); + +static unsigned int sunxi_pcie_ep_func_conf_select(struct sunxi_pcie_ep *ep, + u8 func_no) +{ + struct sunxi_pcie *pcie = to_sunxi_pcie_from_ep(ep); + + WARN_ON(func_no && !pcie->drvdata->func_offset); + return pcie->drvdata->func_offset * func_no; +} + +static const struct sunxi_pcie_ep_ops sunxi_ep_ops = { + .func_conf_select = sunxi_pcie_ep_func_conf_select, +}; + +static const struct sunxi_pcie_of_data sunxi_pcie_rc_v210_of_data = { + .mode = SUNXI_PCIE_RC_TYPE, + .cpu_pcie_addr_quirk = true, +}; + +static const struct sunxi_pcie_of_data sunxi_pcie_rc_v210_v2_of_data = { + .mode = SUNXI_PCIE_RC_TYPE, + .has_pcie_slv_clk = true, + .need_pcie_rst = true, +}; + +static const struct sunxi_pcie_of_data sunxi_pcie_rc_v210_v3_of_data = { + .mode = SUNXI_PCIE_RC_TYPE, + .has_pcie_slv_clk = true, + .need_pcie_rst = true, +}; + +static const struct sunxi_pcie_of_data sunxi_pcie_rc_v300_of_data = { + .mode = SUNXI_PCIE_RC_TYPE, + .has_pcie_slv_clk = true, + .need_pcie_rst = true, + .pcie_slv_clk_400m = true, + .has_pcie_its_clk = true, +}; + +static const struct sunxi_pcie_of_data sunxi_pcie_ep_v210_of_data = { + .mode = SUNXI_PCIE_EP_TYPE, + .func_offset = 0x10000, + .ops = &sunxi_ep_ops, + .has_pcie_slv_clk = true, + .need_pcie_rst = true, +}; + +static const struct sunxi_pcie_of_data sunxi_pcie_ep_v300_of_data = { + .mode = SUNXI_PCIE_EP_TYPE, + .func_offset = 0x10000, + .ops = &sunxi_ep_ops, +}; + +static const struct of_device_id sunxi_pcie_plat_of_match[] = { + { + .compatible = "allwinner,sunxi-pcie-v210-rc", + .data = &sunxi_pcie_rc_v210_of_data, + }, + { + .compatible = "allwinner,sunxi-pcie-v210-v2-rc", + .data = &sunxi_pcie_rc_v210_v2_of_data, + }, + { + .compatible = "allwinner,sunxi-pcie-v210-v3-rc", + .data = &sunxi_pcie_rc_v210_v3_of_data, + }, + { + .compatible = "allwinner,sunxi-pcie-v210-ep", + .data = &sunxi_pcie_ep_v210_of_data, + }, + { + .compatible = "allwinner,sunxi-pcie-v300-rc", + .data = &sunxi_pcie_rc_v300_of_data, + }, + { + .compatible = "allwinner,sunxi-pcie-v300-ep", + .data = &sunxi_pcie_ep_v300_of_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, sunxi_pcie_plat_of_match); + +void sunxi_pcie_plat_ltssm_enable(struct sunxi_pcie *pcie) +{ + u32 val; + + val = sunxi_pcie_readl(pcie, PCIE_LTSSM_CTRL); + val |= PCIE_LINK_TRAINING; + sunxi_pcie_writel(val, pcie, PCIE_LTSSM_CTRL); +} +EXPORT_SYMBOL_GPL(sunxi_pcie_plat_ltssm_enable); + +void sunxi_pcie_plat_ltssm_disable(struct sunxi_pcie *pcie) +{ + u32 val; + + val = sunxi_pcie_readl(pcie, PCIE_LTSSM_CTRL); + val &= ~PCIE_LINK_TRAINING; + sunxi_pcie_writel(val, pcie, PCIE_LTSSM_CTRL); +} +EXPORT_SYMBOL_GPL(sunxi_pcie_plat_ltssm_disable); + +static void sunxi_pcie_plat_irqpending(struct sunxi_pcie_port *pp) +{ + struct sunxi_pcie *pcie = to_sunxi_pcie_from_pp(pp); + u32 val; + + val = sunxi_pcie_readl(pcie, PCIE_INT_ENABLE_CLR); + val &= ~PCIE_LINK_INT_EN; + sunxi_pcie_writel(val, pcie, PCIE_INT_ENABLE_CLR); +} + +static void sunxi_pcie_plat_set_irqmask(struct sunxi_pcie *pci) +{ + u32 val; + + val = sunxi_pcie_readl(pci, PCIE_INT_ENABLE_CLR); + val |= PCIE_LINK_INT_EN; + sunxi_pcie_writel(val, pci, PCIE_INT_ENABLE_CLR); +} + +static int sunxi_pcie_plat_power_on(struct sunxi_pcie *pci) +{ + struct device *dev = pci->dev; + int ret = 0; + + if (!IS_ERR(pci->pcie3v3)) { + ret = regulator_set_voltage(pci->pcie3v3, 3300000, 3300000); + if (ret) + dev_warn(dev, "failed to set regulator voltage\n"); + + ret = regulator_enable(pci->pcie3v3); + if (ret) + dev_err(dev, "failed to enable pcie3v3 regulator\n"); + } + + if (!IS_ERR(pci->pcie1v8)) { + ret = regulator_set_voltage(pci->pcie1v8, 1800000, 1800000); + if (ret) + dev_warn(dev, "failed to set regulator voltage\n"); + + ret = regulator_enable(pci->pcie1v8); + if (ret) + dev_err(dev, "failed to enable pcie1v8 regulator\n"); + } + + return ret; +} + +static void sunxi_pcie_plat_power_off(struct sunxi_pcie *pci) +{ + if (!IS_ERR(pci->pcie3v3)) + regulator_disable(pci->pcie3v3); + if (!IS_ERR(pci->pcie1v8)) + regulator_disable(pci->pcie1v8); +} + +static int sunxi_pcie_plat_clk_setup(struct sunxi_pcie *pci) +{ + int ret; + + if (pci->drvdata->need_pcie_rst) { + ret = reset_control_deassert(pci->pcie_rst); + if (ret) { + dev_err(pci->dev, "cannot reset pcie\n"); + return ret; + } + + ret = reset_control_deassert(pci->pwrup_rst); + if (ret) { + dev_err(pci->dev, "cannot pwrup_reset pcie\n"); + goto err0; + } + } + + ret = clk_prepare_enable(pci->pcie_aux); + if (ret) { + dev_err(pci->dev, "cannot prepare/enable aux clock\n"); + goto err1; + } + + if (pci->drvdata->has_pcie_slv_clk) { + if (pci->drvdata->pcie_slv_clk_400m) { + ret = clk_set_rate(pci->pcie_slv, 400000000); + if (ret) { + dev_err(pci->dev, "cannot set slv clock\n"); + goto err2; + } + } + ret = clk_prepare_enable(pci->pcie_slv); + if (ret) { + dev_err(pci->dev, "cannot prepare/enable slv clock\n"); + goto err2; + } + } + + if (pci->drvdata->has_pcie_its_clk) { + ret = reset_control_deassert(pci->pcie_its_rst); + if (ret) { + dev_err(pci->dev, "cannot reset pcie its\n"); + goto err3; + } + + ret = clk_prepare_enable(pci->pcie_its); + if (ret) { + dev_err(pci->dev, "cannot prepare/enable its clock\n"); + goto err4; + } + } + + return 0; +err4: + if (pci->drvdata->has_pcie_its_clk) + reset_control_assert(pci->pcie_its_rst); +err3: + if (pci->drvdata->has_pcie_slv_clk) + clk_disable_unprepare(pci->pcie_slv); +err2: + clk_disable_unprepare(pci->pcie_aux); +err1: + if (pci->drvdata->need_pcie_rst) + reset_control_assert(pci->pwrup_rst); +err0: + if (pci->drvdata->need_pcie_rst) + reset_control_assert(pci->pcie_rst); + + return ret; +} + +static void sunxi_pcie_plat_clk_exit(struct sunxi_pcie *pci) +{ + if (pci->drvdata->has_pcie_its_clk) { + clk_disable_unprepare(pci->pcie_its); + reset_control_assert(pci->pcie_its_rst); + } + + if (pci->drvdata->has_pcie_slv_clk) + clk_disable_unprepare(pci->pcie_slv); + + clk_disable_unprepare(pci->pcie_aux); + + if (pci->drvdata->need_pcie_rst) { + reset_control_assert(pci->pcie_rst); + reset_control_assert(pci->pwrup_rst); + } +} + +static int sunxi_pcie_plat_clk_get(struct platform_device *pdev, struct sunxi_pcie *pci) +{ + pci->pcie_aux = devm_clk_get(&pdev->dev, "pclk_aux"); + if (IS_ERR(pci->pcie_aux)) { + dev_err(&pdev->dev, "fail to get pclk_aux\n"); + return PTR_ERR(pci->pcie_aux); + } + + if (pci->drvdata->has_pcie_slv_clk) { + pci->pcie_slv = devm_clk_get(&pdev->dev, "pclk_slv"); + if (IS_ERR(pci->pcie_slv)) { + dev_err(&pdev->dev, "fail to get pclk_slv\n"); + return PTR_ERR(pci->pcie_slv); + } + } + + if (pci->drvdata->need_pcie_rst) { + pci->pcie_rst = devm_reset_control_get(&pdev->dev, "pclk_rst"); + if (IS_ERR(pci->pcie_rst)) { + dev_err(&pdev->dev, "fail to get pclk_rst\n"); + return PTR_ERR(pci->pcie_rst); + } + + pci->pwrup_rst = devm_reset_control_get(&pdev->dev, "pwrup_rst"); + if (IS_ERR(pci->pwrup_rst)) { + dev_err(&pdev->dev, "fail to get pwrup_rst\n"); + return PTR_ERR(pci->pwrup_rst); + } + } + + if (pci->drvdata->has_pcie_its_clk) { + pci->pcie_its = devm_clk_get(&pdev->dev, "its"); + if (IS_ERR(pci->pcie_its)) { + dev_err(&pdev->dev, "fail to get its clk\n"); + return PTR_ERR(pci->pcie_its); + } + + pci->pcie_its_rst = devm_reset_control_get(&pdev->dev, "its"); + if (IS_ERR(pci->pcie_its_rst)) { + dev_err(&pdev->dev, "fail to get its rst\n"); + return PTR_ERR(pci->pcie_its_rst); + } + } + return 0; +} + +static int sunxi_pcie_plat_combo_phy_init(struct sunxi_pcie *pci) +{ + int ret; + + ret = phy_init(pci->phy); + if (ret) { + dev_err(pci->dev, "fail to init phy, err %d\n", ret); + return ret; + } + + return 0; +} + +static void sunxi_pcie_plat_combo_phy_deinit(struct sunxi_pcie *pci) +{ + phy_exit(pci->phy); +} + +static void sunxi_pcie_plat_sii_int0_handler(struct sunxi_pcie_port *pp) +{ + struct sunxi_pcie *pci = to_sunxi_pcie_from_pp(pp); + u32 mask, stas, irq; + + mask = sunxi_pcie_readl(pci, SII_INT_MASK0); + stas = sunxi_pcie_readl(pci, SII_INT_STAS0); + irq = mask & stas; + + if (irq & INTX_RX_ASSERT_MASK) { + unsigned long status = irq & INTX_RX_ASSERT_MASK; + u32 bit = INTX_RX_ASSERT_SHIFT; + for_each_set_bit_from(bit, &status, PCI_NUM_INTX + INTX_RX_ASSERT_SHIFT) { + /* Clear INTx status */ + sunxi_pcie_writel(BIT(bit), pci, SII_INT_STAS0); + generic_handle_domain_irq(pp->intx_domain, bit - INTX_RX_ASSERT_SHIFT); + } + } +} + +static irqreturn_t sunxi_pcie_plat_sii_handler(int irq, void *arg) +{ + struct sunxi_pcie_port *pp = (struct sunxi_pcie_port *)arg; + + sunxi_pcie_plat_sii_int0_handler(pp); + + sunxi_pcie_plat_irqpending(pp); + + return IRQ_HANDLED; +} + +static void sunxi_pcie_plat_dma_handle_interrupt(struct sunxi_pcie *pci, u32 ch, enum dma_dir dma_trx) +{ + sunxi_pci_edma_chan_t *edma_chan = NULL; + sunxi_pcie_edma_callback cb = NULL; + void *cb_data = NULL; + + if (dma_trx == PCIE_DMA_WRITE) { + edma_chan = &pci->dma_wr_chn[ch]; + cb = edma_chan->callback; + cb_data = edma_chan->callback_param; + if (cb) + cb(cb_data); + } else if (dma_trx == PCIE_DMA_READ) { + edma_chan = &pci->dma_rd_chn[ch]; + cb = edma_chan->callback; + cb_data = edma_chan->callback_param; + if (cb) + cb(cb_data); + } else { + dev_err(pci->dev, "ERR: unsupported type:%d \n", dma_trx); + } + + if (edma_chan->cookie) + sunxi_pcie_dma_chan_release(edma_chan, dma_trx); +} + +#define SUNXI_PCIE_DMA_IRQ_HANDLER(name, chn, dir) \ +static irqreturn_t sunxi_pcie_##name##_irq_handler \ + (int irq, void *arg) \ +{ \ + struct sunxi_pcie *pci = arg; \ + union int_status sta = {0}; \ + union int_clear clr = {0}; \ + \ + sta.dword = sunxi_pcie_readl_dbi(pci, PCIE_DMA_OFFSET + \ + (dir ? PCIE_DMA_RD_INT_STATUS : PCIE_DMA_WR_INT_STATUS)); \ + \ + if (sta.done & BIT(chn)) { \ + clr.doneclr = BIT(chn); \ + sunxi_pcie_writel_dbi(pci, PCIE_DMA_OFFSET + \ + (dir ? PCIE_DMA_RD_INT_CLEAR : PCIE_DMA_WR_INT_CLEAR), clr.dword);\ + sunxi_pcie_plat_dma_handle_interrupt(pci, chn, dir); \ + } \ + \ + if (sta.abort & BIT(chn)) { \ + clr.abortclr = BIT(chn); \ + sunxi_pcie_writel_dbi(pci, PCIE_DMA_OFFSET + \ + (dir ? PCIE_DMA_RD_INT_CLEAR : PCIE_DMA_WR_INT_CLEAR), clr.dword);\ + dev_err(pci->dev, "DMA %s channel %d is abort\n", \ + dir ? "read":"write", chn); \ + } \ + \ + return IRQ_HANDLED; \ +} + +SUNXI_PCIE_DMA_IRQ_HANDLER(dma_w0, 0, PCIE_DMA_WRITE) +SUNXI_PCIE_DMA_IRQ_HANDLER(dma_w1, 1, PCIE_DMA_WRITE) +SUNXI_PCIE_DMA_IRQ_HANDLER(dma_w2, 2, PCIE_DMA_WRITE) +SUNXI_PCIE_DMA_IRQ_HANDLER(dma_w3, 3, PCIE_DMA_WRITE) + +SUNXI_PCIE_DMA_IRQ_HANDLER(dma_r0, 0, PCIE_DMA_READ) +SUNXI_PCIE_DMA_IRQ_HANDLER(dma_r1, 1, PCIE_DMA_READ) +SUNXI_PCIE_DMA_IRQ_HANDLER(dma_r2, 2, PCIE_DMA_READ) +SUNXI_PCIE_DMA_IRQ_HANDLER(dma_r3, 3, PCIE_DMA_READ) + +static void sunxi_pcie_plat_dma_read(struct sunxi_pcie *pci, struct dma_table *table) +{ + int offset = PCIE_DMA_OFFSET + table->start.chnl * 0x200; + + sunxi_pcie_writel_dbi(pci, PCIE_DMA_OFFSET + PCIE_DMA_RD_ENB, + table->enb.dword); + sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_RD_CTRL_LO, + table->ctx_reg.ctrllo.dword); + sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_RD_CTRL_HI, + table->ctx_reg.ctrlhi.dword); + sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_RD_XFERSIZE, + table->ctx_reg.xfersize); + sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_RD_SAR_LO, + table->ctx_reg.sarptrlo); + sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_RD_SAR_HI, + table->ctx_reg.sarptrhi); + sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_RD_DAR_LO, + table->ctx_reg.darptrlo); + sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_RD_DAR_HI, + table->ctx_reg.darptrhi); + sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_RD_WEILO, + table->weilo.dword); + sunxi_pcie_writel_dbi(pci, PCIE_DMA_OFFSET + PCIE_DMA_RD_DOORBELL, + table->start.dword); +} + +static void sunxi_pcie_plat_dma_write(struct sunxi_pcie *pci, struct dma_table *table) +{ + int offset = PCIE_DMA_OFFSET + table->start.chnl * 0x200; + + sunxi_pcie_writel_dbi(pci, PCIE_DMA_OFFSET + PCIE_DMA_WR_ENB, + table->enb.dword); + sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_WR_CTRL_LO, + table->ctx_reg.ctrllo.dword); + sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_WR_CTRL_HI, + table->ctx_reg.ctrlhi.dword); + sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_WR_XFERSIZE, + table->ctx_reg.xfersize); + sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_WR_SAR_LO, + table->ctx_reg.sarptrlo); + sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_WR_SAR_HI, + table->ctx_reg.sarptrhi); + sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_WR_DAR_LO, + table->ctx_reg.darptrlo); + sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_WR_DAR_HI, + table->ctx_reg.darptrhi); + sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_WR_WEILO, + table->weilo.dword); + sunxi_pcie_writel_dbi(pci, PCIE_DMA_OFFSET + PCIE_DMA_WR_DOORBELL, + table->start.dword); +} + +/* + * DMA controller: I/O and Type 0 or Type 1 configuration DMA + * transfers are not supported. + * Transfer size: 1B - 4GB + */ +static void sunxi_pcie_plat_dma_start(struct dma_table *table, struct dma_trx_obj *obj) +{ + struct sunxi_pcie *pci = dev_get_drvdata(obj->dev); + + if (table->dir == PCIE_DMA_READ) { + sunxi_pcie_plat_dma_read(pci, table); + } else if (table->dir == PCIE_DMA_WRITE) { + sunxi_pcie_plat_dma_write(pci, table); + } +} + +static int sunxi_pcie_plat_dma_config(struct dma_table *table, phys_addr_t src_addr, phys_addr_t dst_addr, + unsigned int size, enum dma_dir dma_trx, sunxi_pci_edma_chan_t *edma_chn) +{ + sunxi_pci_edma_chan_t *chn = NULL; + + table->ctx_reg.ctrllo.lie = 0x1; + table->ctx_reg.ctrllo.rie = 0x0; + table->ctx_reg.ctrllo.td = 0x1; + table->ctx_reg.ctrlhi.dword = 0x0; + table->ctx_reg.xfersize = size; + table->ctx_reg.sarptrlo = (u32)(src_addr & 0xffffffff); + table->ctx_reg.sarptrhi = (u32)(src_addr >> 32); + table->ctx_reg.darptrlo = (u32)(dst_addr & 0xffffffff); + table->ctx_reg.darptrhi = (u32)(dst_addr >> 32); + table->start.stop = 0x0; + table->dir = dma_trx; + + if (!edma_chn) { + chn = (sunxi_pci_edma_chan_t *)sunxi_pcie_dma_chan_request(dma_trx, NULL, NULL); + if (!chn) { + dev_err(NULL, "pcie request %s channel error! \n", (dma_trx ? "DMA_READ" : "DMA_WRITE")); + return -ENOMEM; + } + + chn->cookie = true; + table->start.chnl = chn->chnl_num; + table->weilo.dword = (PCIE_WEIGHT << (5 * chn->chnl_num)); + } else { + table->start.chnl = edma_chn->chnl_num; + table->weilo.dword = (PCIE_WEIGHT << (5 * edma_chn->chnl_num)); + } + + table->enb.enb = 0x1; + return 0; +} + +static int sunxi_pcie_plat_request_irq(struct sunxi_pcie *sunxi_pcie, struct platform_device *pdev) +{ + int irq, ret; + + irq = platform_get_irq_byname(pdev, "sii"); + if (irq < 0) + return -EINVAL; + + ret = devm_request_irq(&pdev->dev, irq, + sunxi_pcie_plat_sii_handler, IRQF_SHARED, "pcie-sii", &sunxi_pcie->pp); + if (ret) { + dev_err(&pdev->dev, "PCIe failed to request linkup IRQ\n"); + return ret; + } + + ret = sunxi_pcie_dma_get_chan(pdev); + if (ret) + return -EINVAL; + + switch (sunxi_pcie->num_edma) { + case 4: + irq = platform_get_irq_byname(pdev, "edma-w3"); + if (irq < 0) + return -EINVAL; + + ret = devm_request_irq(&pdev->dev, irq, sunxi_pcie_dma_w3_irq_handler, + IRQF_SHARED, "pcie-dma-w3", sunxi_pcie); + if (ret) { + dev_err(&pdev->dev, "failed to request PCIe DMA IRQ\n"); + return ret; + } + + irq = platform_get_irq_byname(pdev, "edma-r3"); + if (irq < 0) + return -EINVAL; + + ret = devm_request_irq(&pdev->dev, irq, sunxi_pcie_dma_r3_irq_handler, + IRQF_SHARED, "pcie-dma-r3", sunxi_pcie); + if (ret) { + dev_err(&pdev->dev, "failed to request PCIe DMA IRQ\n"); + return ret; + } + + fallthrough; + case 3: + irq = platform_get_irq_byname(pdev, "edma-w2"); + if (irq < 0) + return -EINVAL; + + ret = devm_request_irq(&pdev->dev, irq, sunxi_pcie_dma_w2_irq_handler, + IRQF_SHARED, "pcie-dma-w2", sunxi_pcie); + if (ret) { + dev_err(&pdev->dev, "failed to request PCIe DMA IRQ\n"); + return ret; + } + + irq = platform_get_irq_byname(pdev, "edma-r2"); + if (irq < 0) + return -EINVAL; + + ret = devm_request_irq(&pdev->dev, irq, sunxi_pcie_dma_r2_irq_handler, + IRQF_SHARED, "pcie-dma-r2", sunxi_pcie); + if (ret) { + dev_err(&pdev->dev, "failed to request PCIe DMA IRQ\n"); + return ret; + } + + fallthrough; + case 2: + irq = platform_get_irq_byname(pdev, "edma-w1"); + if (irq < 0) + return -EINVAL; + + ret = devm_request_irq(&pdev->dev, irq, sunxi_pcie_dma_w1_irq_handler, + IRQF_SHARED, "pcie-dma-w1", sunxi_pcie); + if (ret) { + dev_err(&pdev->dev, "failed to request PCIe DMA IRQ\n"); + return ret; + } + + irq = platform_get_irq_byname(pdev, "edma-r1"); + if (irq < 0) + return -EINVAL; + + ret = devm_request_irq(&pdev->dev, irq, sunxi_pcie_dma_r1_irq_handler, + IRQF_SHARED, "pcie-dma-r1", sunxi_pcie); + if (ret) { + dev_err(&pdev->dev, "failed to request PCIe DMA IRQ\n"); + return ret; + } + + fallthrough; + case 1: + irq = platform_get_irq_byname(pdev, "edma-w0"); + if (irq < 0) + return -EINVAL; + + ret = devm_request_irq(&pdev->dev, irq, sunxi_pcie_dma_w0_irq_handler, + IRQF_SHARED, "pcie-dma-w0", sunxi_pcie); + if (ret) { + dev_err(&pdev->dev, "failed to request PCIe DMA IRQ\n"); + return ret; + } + + irq = platform_get_irq_byname(pdev, "edma-r0"); + if (irq < 0) + return -EINVAL; + + ret = devm_request_irq(&pdev->dev, irq, sunxi_pcie_dma_r0_irq_handler, + IRQF_SHARED, "pcie-dma-r0", sunxi_pcie); + if (ret) { + dev_err(&pdev->dev, "failed to request PCIe DMA IRQ\n"); + return ret; + } + + break; + default: + dev_err(sunxi_pcie->dev, "Not support DMA chan_num[%d], which exceed chan_range [%d-%d]\n", + sunxi_pcie->num_edma, 1, 4); + return -EINVAL; + } + + return 0; +} + +static int sunxi_pcie_plat_dma_init(struct sunxi_pcie *pci) +{ + pci->dma_obj = sunxi_pcie_dma_obj_probe(pci->dev); + + if (IS_ERR(pci->dma_obj)) { + dev_err(pci->dev, "failed to prepare dma obj probe\n"); + return -EINVAL; + } + + sunxi_pcie_writel_dbi(pci, PCIE_DMA_OFFSET + PCIE_DMA_WR_INT_MASK, 0x0); + sunxi_pcie_writel_dbi(pci, PCIE_DMA_OFFSET + PCIE_DMA_RD_INT_MASK, 0x0); + return 0; +} + +static void sunxi_pcie_plat_dma_deinit(struct sunxi_pcie *pci) +{ + sunxi_pcie_dma_obj_remove(pci->dev); + + sunxi_pcie_writel_dbi(pci, PCIE_DMA_OFFSET + PCIE_DMA_WR_INT_MASK, PCIE_DMA_INT_MASK); + sunxi_pcie_writel_dbi(pci, PCIE_DMA_OFFSET + PCIE_DMA_RD_INT_MASK, PCIE_DMA_INT_MASK); +} + +static int sunxi_pcie_plat_parse_dts_res(struct platform_device *pdev, struct sunxi_pcie *pci) +{ + struct sunxi_pcie_port *pp = &pci->pp; + struct device_node *np = pp->dev->of_node; + struct resource *dbi_res; + int ret; + + dbi_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi"); + if (!dbi_res) { + dev_err(&pdev->dev, "get pcie dbi failed\n"); + return -ENODEV; + } + + pci->dbi_base = devm_ioremap_resource(&pdev->dev, dbi_res); + if (IS_ERR(pci->dbi_base)) { + dev_err(&pdev->dev, "ioremap pcie dbi failed\n"); + return PTR_ERR(pci->dbi_base); + } + + pp->dbi_base = pci->dbi_base; + pci->app_base = pci->dbi_base + PCIE_USER_DEFINED_REGISTER; + + pci->link_gen = of_pci_get_max_link_speed(pdev->dev.of_node); + if (pci->link_gen < 0) { + dev_warn(&pdev->dev, "get pcie speed Gen failed\n"); + pci->link_gen = 0x1; + } + + pci->rst_gpio = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(pci->rst_gpio)) + dev_warn(&pdev->dev, "Failed to get \"reset-gpios\"\n"); + else + gpiod_direction_output(pci->rst_gpio, 1); + + pci->wake_gpio = devm_gpiod_get(&pdev->dev, "wake", GPIOD_OUT_HIGH); + if (IS_ERR(pci->wake_gpio)) + dev_warn(&pdev->dev, "Failed to get \"wake-gpios\"\n"); + else + gpiod_direction_output(pci->wake_gpio, 1); + + pci->pcie3v3 = devm_regulator_get_optional(&pdev->dev, "pcie3v3"); + if (IS_ERR(pci->pcie3v3)) + dev_warn(&pdev->dev, "no pcie3v3 regulator found\n"); + + pci->pcie1v8 = devm_regulator_get_optional(&pdev->dev, "pcie1v8"); + if (IS_ERR(pci->pcie1v8)) + dev_warn(&pdev->dev, "no pcie1v8 regulator found\n"); + + ret = of_property_read_u32(np, "num-lanes", &pci->lanes); + if (ret) { + dev_err(&pdev->dev, "Failed to parse the number of lanes\n"); + return -EINVAL; + } + + pp->cpu_pcie_addr_quirk = pci->drvdata->cpu_pcie_addr_quirk; + + ret = sunxi_pcie_plat_clk_get(pdev, pci); + if (ret) { + dev_err(&pdev->dev, "pcie get clk init failed\n"); + return -ENODEV; + } + + pci->phy = devm_phy_get(pci->dev, "pcie-phy"); + if (IS_ERR(pci->phy)) + return dev_err_probe(pci->dev, PTR_ERR(pci->phy), "missing PHY\n"); + + return 0; +} + +static int sunxi_pcie_plat_hw_init(struct sunxi_pcie *pci) +{ + int ret; + + ret = sunxi_pcie_plat_power_on(pci); + if (ret) + return ret; + + ret = sunxi_pcie_plat_clk_setup(pci); + if (ret) + goto err0; + + ret = sunxi_pcie_plat_combo_phy_init(pci); + if (ret) + goto err1; + + return 0; + +err1: + sunxi_pcie_plat_clk_exit(pci); +err0: + sunxi_pcie_plat_power_off(pci); + + return ret; +} + +static void sunxi_pcie_plat_hw_deinit(struct sunxi_pcie *pci) +{ + sunxi_pcie_plat_combo_phy_deinit(pci); + sunxi_pcie_plat_power_off(pci); + sunxi_pcie_plat_clk_exit(pci); +} + +static int sunxi_pcie_plat_probe(struct platform_device *pdev) +{ + struct sunxi_pcie *pci; + struct sunxi_pcie_port *pp; + const struct sunxi_pcie_of_data *data; + enum sunxi_pcie_device_mode mode; + int ret; + + data = of_device_get_match_data(&pdev->dev); + mode = (enum sunxi_pcie_device_mode)data->mode; + + pci = devm_kzalloc(&pdev->dev, sizeof(*pci), GFP_KERNEL); + if (!pci) + return -ENOMEM; + + pp = &pci->pp; + pp->dev = &pdev->dev; + pci->dev = &pdev->dev; + pci->drvdata = data; + + ret = sunxi_pcie_plat_parse_dts_res(pdev, pci); + if (ret) + return ret; + + ret = sunxi_pcie_plat_hw_init(pci); + if (ret) + return ret; + + sunxi_pcie_plat_set_irqmask(pci); + platform_set_drvdata(pdev, pci); + + ret = sunxi_pcie_plat_request_irq(pci, pdev); + if (ret) + goto err0; + + pm_runtime_enable(&pdev->dev); + ret = pm_runtime_get_sync(&pdev->dev); + if (ret < 0) { + dev_err(&pdev->dev, "pm_runtime_get_sync failed\n"); + goto err1; + } + + ret = sunxi_pcie_plat_dma_init(pci); + if (ret) + goto err2; + + if (pci->dma_obj) { + pci->dma_obj->start_dma_trx_func = sunxi_pcie_plat_dma_start; + pci->dma_obj->config_dma_trx_func = sunxi_pcie_plat_dma_config; + } + + switch (pci->drvdata->mode) { + case SUNXI_PCIE_RC_TYPE: + ret = sunxi_pcie_host_add_port(pci, pdev); + break; + case SUNXI_PCIE_EP_TYPE: + sunxi_pcie_plat_set_mode(pci); + pci->ep.ops = &sunxi_ep_ops; + ret = sunxi_pcie_ep_init(pci); + break; + default: + dev_err(&pdev->dev, "INVALID device type %d\n", pci->drvdata->mode); + ret = -EINVAL; + break; + } + + if (ret) + goto err3; + + dev_info(&pdev->dev, "driver version: %s\n", SUNXI_PCIE_MODULE_VERSION); + + return 0; + +err3: + sunxi_pcie_plat_dma_deinit(pci); +err2: + pm_runtime_put(&pdev->dev); +err1: + pm_runtime_disable(&pdev->dev); +err0: + sunxi_pcie_plat_hw_deinit(pci); + + return ret; +} + +static void sunxi_pcie_plat_remove(struct platform_device *pdev) +{ + struct sunxi_pcie *pci = platform_get_drvdata(pdev); + + sunxi_pcie_plat_hw_deinit(pci); + + pm_runtime_disable(&pdev->dev); + + pm_runtime_put(&pdev->dev); + + sunxi_pcie_plat_dma_deinit(pci); + + switch (pci->drvdata->mode) { + case SUNXI_PCIE_RC_TYPE: + sunxi_pcie_host_remove_port(pci); + break; + case SUNXI_PCIE_EP_TYPE: + sunxi_pcie_ep_deinit(pci); + break; + default: + dev_err(&pdev->dev, "unspport device type %d\n", pci->drvdata->mode); + break; + } + + sunxi_pcie_plat_ltssm_disable(pci); +} + +#if IS_ENABLED(CONFIG_PM) +static int sunxi_pcie_plat_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct sunxi_pcie *pci = platform_get_drvdata(pdev); + + sunxi_pcie_plat_ltssm_disable(pci); + + usleep_range(200, 300); + + sunxi_pcie_plat_hw_deinit(pci); + + return 0; +} + +static int sunxi_pcie_plat_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct sunxi_pcie *pci = platform_get_drvdata(pdev); + struct sunxi_pcie_port *pp = &pci->pp; + int ret; + + ret = sunxi_pcie_plat_hw_init(pci); + if (ret) + return -EINVAL; + + /* TODO */ + usleep_range(100, 300); + + switch (pci->drvdata->mode) { + case SUNXI_PCIE_RC_TYPE: + sunxi_pcie_plat_ltssm_disable(pci); + sunxi_pcie_host_setup_rc(pp); + + if (IS_ENABLED(CONFIG_PCI_MSI) && !pp->has_its) { + phys_addr_t pa = ALIGN_DOWN(virt_to_phys(pp), SZ_4K); + sunxi_pcie_host_wr_own_conf(pp, PCIE_MSI_ADDR_LO, 4, lower_32_bits(pa)); + sunxi_pcie_host_wr_own_conf(pp, PCIE_MSI_ADDR_HI, 4, upper_32_bits(pa)); + } + + sunxi_pcie_host_establish_link(pci); + sunxi_pcie_host_speed_change(pci, pci->link_gen); + break; + case SUNXI_PCIE_EP_TYPE: + /* TODO */ + break; + default: + dev_err(pci->dev, "unsupport device type %d\n", pci->drvdata->mode); + break; + } + + return 0; +} + +static struct dev_pm_ops sunxi_pcie_plat_pm_ops = { + .suspend = sunxi_pcie_plat_suspend, + .resume = sunxi_pcie_plat_resume, +}; +#else +static struct dev_pm_ops sunxi_pcie_plat_pm_ops; +#endif /* CONFIG_PM */ + +static struct platform_driver sunxi_pcie_plat_driver = { + .driver = { + .name = "sunxi-pcie", + .owner = THIS_MODULE, + .of_match_table = sunxi_pcie_plat_of_match, + .pm = &sunxi_pcie_plat_pm_ops, + }, + .probe = sunxi_pcie_plat_probe, + .remove = sunxi_pcie_plat_remove, +}; + +module_platform_driver(sunxi_pcie_plat_driver); + +MODULE_AUTHOR("songjundong "); +MODULE_DESCRIPTION("Allwinner PCIe controller platform driver"); +MODULE_VERSION(SUNXI_PCIE_MODULE_VERSION); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/controller/allwinner/pcie-sunxi-rc.c b/drivers/pci/controller/allwinner/pcie-sunxi-rc.c new file mode 100644 index 00000000000000..90e2bc0abffe87 --- /dev/null +++ b/drivers/pci/controller/allwinner/pcie-sunxi-rc.c @@ -0,0 +1,830 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ +// SPDX_License-Identifier: GPL-2.0 +/* + * allwinner PCIe host controller driver + * + * Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. + * + * Author: songjundong + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define SUNXI_MODNAME "pcie-rc" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../pci.h" +#include "pcie-sunxi.h" +#include "pcie-sunxi-dma.h" + +static bool sunxi_pcie_host_is_link_up(struct sunxi_pcie_port *pp) +{ + if (pp->ops->is_link_up) + return pp->ops->is_link_up(pp); + else + return false; +} + +static int sunxi_pcie_host_rd_own_conf(struct sunxi_pcie_port *pp, int where, int size, u32 *val) +{ + int ret; + + if (pp->ops->rd_own_conf) + ret = pp->ops->rd_own_conf(pp, where, size, val); + else + ret = sunxi_pcie_cfg_read(pp->dbi_base + where, size, val); + + return ret; +} + +int sunxi_pcie_host_wr_own_conf(struct sunxi_pcie_port *pp, int where, int size, u32 val) +{ + int ret; + + if (pp->ops->wr_own_conf) + ret = pp->ops->wr_own_conf(pp, where, size, val); + else + ret = sunxi_pcie_cfg_write(pp->dbi_base + where, size, val); + + return ret; +} + +static void sunxi_msi_top_irq_ack(struct irq_data *d) +{ + /* NULL */ +} + +static struct irq_chip sunxi_msi_top_chip = { + .name = "SUNXI-PCIe-MSI", + .irq_ack = sunxi_msi_top_irq_ack, + .irq_mask = pci_msi_mask_irq, + .irq_unmask = pci_msi_unmask_irq, +}; + +static int sunxi_msi_set_affinity(struct irq_data *d, const struct cpumask *mask, bool force) +{ + return -EINVAL; +} + +static void sunxi_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) +{ + struct sunxi_pcie_port *pcie = irq_data_get_irq_chip_data(data); + u64 msi_target = (u64)pcie->msi_data; + + msg->address_lo = lower_32_bits(msi_target); + msg->address_hi = upper_32_bits(msi_target); + msg->data = data->hwirq; + + dev_dbg(pcie->dev, "msi#%d address_hi %#x address_lo %#x\n", + (int)data->hwirq, msg->address_hi, msg->address_lo); +} + +/* + * whether the following interface needs to be added on the driver: + * .irq_ack, .irq_mask, .irq_unmask and the xxx_bottom_irq_chip. + */ +static struct irq_chip sunxi_msi_bottom_chip = { + .name = "SUNXI MSI", + .irq_set_affinity = sunxi_msi_set_affinity, + .irq_compose_msi_msg = sunxi_compose_msi_msg, +}; + +static int sunxi_msi_domain_alloc(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs, void *args) +{ + struct sunxi_pcie_port *pp = domain->host_data; + int hwirq, i; + unsigned long flags; + + raw_spin_lock_irqsave(&pp->lock, flags); + + hwirq = bitmap_find_free_region(pp->msi_map, INT_PCI_MSI_NR, order_base_2(nr_irqs)); + + raw_spin_unlock_irqrestore(&pp->lock, flags); + + if (unlikely(hwirq < 0)) { + dev_err(pp->dev, "failed to alloc hwirq\n"); + return -ENOSPC; + } + + for (i = 0; i < nr_irqs; i++) + irq_domain_set_info(domain, virq + i, hwirq + i, + &sunxi_msi_bottom_chip, pp, + handle_edge_irq, NULL, NULL); + + return 0; +} + +static void sunxi_msi_domain_free(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs) +{ + struct irq_data *d = irq_domain_get_irq_data(domain, virq); + struct sunxi_pcie_port *pp = domain->host_data; + unsigned long flags; + + raw_spin_lock_irqsave(&pp->lock, flags); + + bitmap_release_region(pp->msi_map, d->hwirq, order_base_2(nr_irqs)); + + raw_spin_unlock_irqrestore(&pp->lock, flags); +} + +static const struct irq_domain_ops sunxi_msi_domain_ops = { + .alloc = sunxi_msi_domain_alloc, + .free = sunxi_msi_domain_free, +}; + +static struct msi_domain_info sunxi_msi_info = { + .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | MSI_FLAG_MULTI_PCI_MSI), + .chip = &sunxi_msi_top_chip, +}; + +static int sunxi_allocate_msi_domains(struct sunxi_pcie_port *pp) +{ + struct fwnode_handle *fwnode = dev_fwnode(pp->dev); + + pp->irq_domain = irq_domain_create_linear(fwnode, INT_PCI_MSI_NR, + &sunxi_msi_domain_ops, pp); + if (!pp->irq_domain) { + dev_err(pp->dev, "failed to create IRQ domain\n"); + return -ENOMEM; + } + irq_domain_update_bus_token(pp->irq_domain, DOMAIN_BUS_NEXUS); + + pp->msi_domain = pci_msi_create_irq_domain(fwnode, &sunxi_msi_info, pp->irq_domain); + if (!pp->msi_domain) { + dev_err(pp->dev, "failed to create MSI domain\n"); + irq_domain_remove(pp->irq_domain); + return -ENOMEM; + } + + return 0; +} + +static void sunxi_free_msi_domains(struct sunxi_pcie_port *pp) +{ + irq_domain_remove(pp->msi_domain); + irq_domain_remove(pp->irq_domain); +} + +static int sunxi_pcie_msi_init(struct sunxi_pcie_port *pp) +{ + u64 msi_target; + int ret; + + ret = dma_set_mask(pp->dev, DMA_BIT_MASK(32)); + if (ret) + dev_warn(pp->dev, "Failed to set DMA mask to 32-bit. Devices with only 32-bit MSI support may not work properly\n"); + + pp->msi_data = dma_map_single_attrs(pp->dev, &pp->msi_msg, sizeof(pp->msi_msg), + DMA_FROM_DEVICE, DMA_ATTR_SKIP_CPU_SYNC); + ret = dma_mapping_error(pp->dev, pp->msi_data); + if (ret) { + dev_err(pp->dev, "Failed to map MSI data\n"); + pp->msi_data = 0; + return ret; + } + + msi_target = (u64)pp->msi_data; + sunxi_pcie_host_wr_own_conf(pp, PCIE_MSI_ADDR_LO, 4, lower_32_bits(msi_target)); + sunxi_pcie_host_wr_own_conf(pp, PCIE_MSI_ADDR_HI, 4, upper_32_bits(msi_target)); + + return 0; +} + +static void sunxi_pcie_free_msi(struct sunxi_pcie_port *pp) +{ + if (pp->msi_data) + dma_unmap_single_attrs(pp->dev, pp->msi_data, sizeof(pp->msi_msg), + DMA_FROM_DEVICE, DMA_ATTR_SKIP_CPU_SYNC); +} + +static void sunxi_pcie_intx_irq_mask(struct irq_data *data) +{ + struct sunxi_pcie *pcie = irq_data_get_irq_chip_data(data); + struct sunxi_pcie_port *pp = &pcie->pp; + irq_hw_number_t hwirq = irqd_to_hwirq(data); + unsigned long flags; + u32 mask, stas; + + raw_spin_lock_irqsave(&pp->lock, flags); + mask = sunxi_pcie_readl(pcie, SII_INT_MASK0); + mask &= ~INTX_RX_ASSERT(hwirq); + sunxi_pcie_writel(mask, pcie, SII_INT_MASK0); + stas = sunxi_pcie_readl(pcie, SII_INT_STAS0); + stas |= INTX_RX_ASSERT(hwirq); + sunxi_pcie_writel(stas, pcie, SII_INT_STAS0); + raw_spin_unlock_irqrestore(&pp->lock, flags); +} + +static void sunxi_pcie_intx_irq_unmask(struct irq_data *data) +{ + struct sunxi_pcie *pcie = irq_data_get_irq_chip_data(data); + struct sunxi_pcie_port *pp = &pcie->pp; + irq_hw_number_t hwirq = irqd_to_hwirq(data); + unsigned long flags; + u32 mask, stas; + + raw_spin_lock_irqsave(&pp->lock, flags); + stas = sunxi_pcie_readl(pcie, SII_INT_STAS0); + stas |= INTX_RX_ASSERT(hwirq); + sunxi_pcie_writel(stas, pcie, SII_INT_STAS0); + mask = sunxi_pcie_readl(pcie, SII_INT_MASK0); + mask |= INTX_RX_ASSERT(hwirq); + sunxi_pcie_writel(mask, pcie, SII_INT_MASK0); + raw_spin_unlock_irqrestore(&pp->lock, flags); +} + +static struct irq_chip sunxi_pcie_sii_intx_chip = { + .name = "SUNXI-PCIe-SII-INTx", + .irq_enable = sunxi_pcie_intx_irq_unmask, + .irq_disable = sunxi_pcie_intx_irq_mask, + .irq_mask = sunxi_pcie_intx_irq_mask, + .irq_unmask = sunxi_pcie_intx_irq_unmask, +}; + +static int sunxi_pcie_intx_map(struct irq_domain *domain, unsigned int irq, + irq_hw_number_t hwirq) +{ + irq_set_chip_and_handler(irq, &sunxi_pcie_sii_intx_chip, handle_simple_irq); + irq_set_chip_data(irq, domain->host_data); + + return 0; +} + +static const struct irq_domain_ops intx_domain_ops = { + .map = sunxi_pcie_intx_map, +}; + +static int sunxi_allocate_intx_domains(struct sunxi_pcie_port *pp) +{ + struct sunxi_pcie *pci = to_sunxi_pcie_from_pp(pp); + struct device_node *intc_node; + u32 val; + + intc_node = of_get_child_by_name(pp->dev->of_node, "legacy-interrupt-controller"); + if (!intc_node) { + dev_warn(pp->dev, "failed to found pcie intc node\n"); + return -ENODEV; + } + + pp->intx_domain = irq_domain_add_linear(intc_node, PCI_NUM_INTX, + &intx_domain_ops, pci); + of_node_put(intc_node); + if (!pp->intx_domain) { + dev_warn(pp->dev, "failed to add intx irq domain\n"); + return -ENODEV; + } + + /* intx irq enable */ + val = sunxi_pcie_readl(pci, SII_INT_MASK0); + val |= INTX_RX_ASSERT_MASK; + sunxi_pcie_writel(val, pci, SII_INT_MASK0); + + return 0; +} + +static void sunxi_free_intx_domains(struct sunxi_pcie_port *pp) +{ + if (pp->intx_domain) + irq_domain_remove(pp->intx_domain); +} + +static void sunxi_pcie_prog_outbound_atu(struct sunxi_pcie_port *pp, int index, int type, + u64 cpu_addr, u64 pci_addr, u32 size) +{ + struct sunxi_pcie *pci = to_sunxi_pcie_from_pp(pp); + unsigned int retries; + int val; + + sunxi_pcie_writel_dbi(pci, PCIE_ATU_LOWER_BASE_OUTBOUND(index), lower_32_bits(cpu_addr)); + sunxi_pcie_writel_dbi(pci, PCIE_ATU_UPPER_BASE_OUTBOUND(index), upper_32_bits(cpu_addr)); + sunxi_pcie_writel_dbi(pci, PCIE_ATU_LIMIT_OUTBOUND(index), lower_32_bits(cpu_addr + size - 1)); + sunxi_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET_OUTBOUND(index), lower_32_bits(pci_addr)); + sunxi_pcie_writel_dbi(pci, PCIE_ATU_UPPER_TARGET_OUTBOUND(index), upper_32_bits(pci_addr)); + sunxi_pcie_writel_dbi(pci, PCIE_ATU_CR1_OUTBOUND(index), type); + sunxi_pcie_writel_dbi(pci, PCIE_ATU_CR2_OUTBOUND(index), PCIE_ATU_ENABLE); + + for (retries = 0; retries < LINK_WAIT_MAX_RETRIE; retries++) { + val = sunxi_pcie_readl_dbi(pci, PCIE_ATU_CR2_OUTBOUND(index)); + + if (val & PCIE_ATU_ENABLE) + return; + + mdelay(WAIT_ATU); + } + dev_warn(pp->dev, "Outbound iATU is not being enabled\n"); +} + +static int sunxi_pcie_rd_other_conf(struct sunxi_pcie_port *pp, struct pci_bus *bus, + u32 devfn, int where, int size, u32 *val) +{ + int ret = PCIBIOS_SUCCESSFUL, type; + u64 busdev; + + busdev = PCIE_ATU_BUS(bus->number) | PCIE_ATU_DEV(PCI_SLOT(devfn)) | + PCIE_ATU_FUNC(PCI_FUNC(devfn)); + + if (pci_is_root_bus(bus->parent)) + type = PCIE_ATU_TYPE_CFG0; + else + type = PCIE_ATU_TYPE_CFG1; + + sunxi_pcie_prog_outbound_atu(pp, PCIE_ATU_INDEX0, type, pp->cfg0_base, busdev, pp->cfg0_size); + + ret = sunxi_pcie_cfg_read(pp->va_cfg0_base + where, size, val); + + return ret; +} + +static int sunxi_pcie_wr_other_conf(struct sunxi_pcie_port *pp, struct pci_bus *bus, + u32 devfn, int where, int size, u32 val) +{ + int ret = PCIBIOS_SUCCESSFUL, type; + u64 busdev; + + busdev = PCIE_ATU_BUS(bus->number) | PCIE_ATU_DEV(PCI_SLOT(devfn)) | + PCIE_ATU_FUNC(PCI_FUNC(devfn)); + + if (pci_is_root_bus(bus->parent)) + type = PCIE_ATU_TYPE_CFG0; + else + type = PCIE_ATU_TYPE_CFG1; + + sunxi_pcie_prog_outbound_atu(pp, PCIE_ATU_INDEX0, type, pp->cfg0_base, busdev, pp->cfg0_size); + + ret = sunxi_pcie_cfg_write(pp->va_cfg0_base + where, size, val); + + return ret; +} + +static int sunxi_pcie_valid_config(struct sunxi_pcie_port *pp, + struct pci_bus *bus, int dev) +{ + /* If there is no link, then there is no device */ + if (!pci_is_root_bus(bus)) { + if (!sunxi_pcie_host_is_link_up(pp)) + return 0; + } else if (dev > 0) + /* Access only one slot on each root port */ + return 0; + + return 1; +} + +static int sunxi_pcie_rd_conf(struct pci_bus *bus, u32 devfn, int where, + int size, u32 *val) +{ + struct sunxi_pcie_port *pp = (bus->sysdata); + int ret; + + if (!pp) + BUG(); + + if (!sunxi_pcie_valid_config(pp, bus, PCI_SLOT(devfn))) { + *val = 0xffffffff; + return PCIBIOS_DEVICE_NOT_FOUND; + } + + if (!pci_is_root_bus(bus)) + ret = sunxi_pcie_rd_other_conf(pp, bus, devfn, + where, size, val); + else + ret = sunxi_pcie_host_rd_own_conf(pp, where, size, val); + + return ret; +} + +static int sunxi_pcie_wr_conf(struct pci_bus *bus, u32 devfn, + int where, int size, u32 val) +{ + struct sunxi_pcie_port *pp = (bus->sysdata); + int ret; + + if (!pp) + BUG(); + + if (sunxi_pcie_valid_config(pp, bus, PCI_SLOT(devfn)) == 0) + return PCIBIOS_DEVICE_NOT_FOUND; + + if (!pci_is_root_bus(bus)) + ret = sunxi_pcie_wr_other_conf(pp, bus, devfn, + where, size, val); + else + ret = sunxi_pcie_host_wr_own_conf(pp, where, size, val); + + return ret; +} + +static struct pci_ops sunxi_pcie_ops = { + .read = sunxi_pcie_rd_conf, + .write = sunxi_pcie_wr_conf, +}; + +static int sunxi_pcie_host_init(struct sunxi_pcie_port *pp) +{ + struct device *dev = pp->dev; + struct resource_entry *win; + struct pci_host_bridge *bridge; + int ret; + + bridge = devm_pci_alloc_host_bridge(dev, 0); + if (!bridge) { + dev_err(dev, "Failed to alloc host bridge\n"); + return -ENOMEM; + } + + pp->bridge = bridge; + + /* Get the I/O and memory ranges from DT */ + resource_list_for_each_entry(win, &bridge->windows) { + switch (resource_type(win->res)) { + case IORESOURCE_IO: + pp->io_size = resource_size(win->res); + pp->io_bus_addr = win->res->start - win->offset; + pp->io_base = pci_pio_to_address(win->res->start); + break; + case 0: + pp->cfg0_size = resource_size(win->res); + pp->cfg0_base = win->res->start; + break; + } + } + + if (!pp->va_cfg0_base) { + pp->va_cfg0_base = devm_pci_remap_cfgspace(dev, + pp->cfg0_base, pp->cfg0_size); + if (!pp->va_cfg0_base) { + dev_err(dev, "Error with ioremap in function\n"); + return -ENOMEM; + } + } + + if (pp->cpu_pcie_addr_quirk) { + pp->cfg0_base -= PCIE_CPU_BASE; + pp->io_base -= PCIE_CPU_BASE; + } + + sunxi_allocate_intx_domains(pp); + + if (pci_msi_enabled() && !pp->has_its) { + ret = sunxi_allocate_msi_domains(pp); + if (ret) + return ret; + + ret = sunxi_pcie_msi_init(pp); + if (ret) + return ret; + } + + if (pp->ops->host_init) + pp->ops->host_init(pp); + + bridge->sysdata = pp; + bridge->ops = &sunxi_pcie_ops; + + ret = pci_host_probe(bridge); + + if (ret) { + if (pci_msi_enabled() && !pp->has_its) { + sunxi_pcie_free_msi(pp); + sunxi_free_msi_domains(pp); + } + sunxi_free_intx_domains(pp); + + dev_err(pp->dev, "Failed to probe host bridge\n"); + + return ret; + } + + return 0; +} + +void sunxi_pcie_host_setup_rc(struct sunxi_pcie_port *pp) +{ + u32 val, i; + int atu_idx = 0; + struct resource_entry *entry; + phys_addr_t mem_base; + struct sunxi_pcie *pci = to_sunxi_pcie_from_pp(pp); + + sunxi_pcie_plat_set_rate(pci); + + /* setup RC BARs */ + sunxi_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_0, 0x4); + sunxi_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_1, 0x0); + + /* setup interrupt pins */ + val = sunxi_pcie_readl_dbi(pci, PCI_INTERRUPT_LINE); + val &= PCIE_INTERRUPT_LINE_MASK; + val |= PCIE_INTERRUPT_LINE_ENABLE; + sunxi_pcie_writel_dbi(pci, PCI_INTERRUPT_LINE, val); + + /* setup bus numbers */ + val = sunxi_pcie_readl_dbi(pci, PCI_PRIMARY_BUS); + val &= 0xff000000; + val |= 0x00ff0100; + sunxi_pcie_writel_dbi(pci, PCI_PRIMARY_BUS, val); + + /* setup command register */ + val = sunxi_pcie_readl_dbi(pci, PCI_COMMAND); + + val &= PCIE_HIGH16_MASK; + val |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY | + PCI_COMMAND_MASTER | PCI_COMMAND_SERR; + + sunxi_pcie_writel_dbi(pci, PCI_COMMAND, val); + + if (pci_msi_enabled() && !pp->has_its) { + for (i = 0; i < 8; i++) { + sunxi_pcie_host_wr_own_conf(pp, PCIE_MSI_INTR_ENABLE(i), 4, ~0); + } + } + + resource_list_for_each_entry(entry, &pp->bridge->windows) { + if (resource_type(entry->res) != IORESOURCE_MEM) + continue; + + if (pp->num_ob_windows <= ++atu_idx) + break; + + if (pp->cpu_pcie_addr_quirk) + mem_base = entry->res->start - PCIE_CPU_BASE; + else + mem_base = entry->res->start; + + sunxi_pcie_prog_outbound_atu(pp, atu_idx, PCIE_ATU_TYPE_MEM, mem_base, + entry->res->start - entry->offset, + resource_size(entry->res)); + } + + if (pp->io_size) { + if (pp->num_ob_windows > ++atu_idx) + sunxi_pcie_prog_outbound_atu(pp, atu_idx, PCIE_ATU_TYPE_IO, pp->io_base, + pp->io_bus_addr, pp->io_size); + else + dev_err(pp->dev, "Resources exceed number of ATU entries (%d)", + pp->num_ob_windows); + } + + sunxi_pcie_host_wr_own_conf(pp, PCI_BASE_ADDRESS_0, 4, 0); + + sunxi_pcie_dbi_ro_wr_en(pci); + + sunxi_pcie_host_wr_own_conf(pp, PCI_CLASS_DEVICE, 2, PCI_CLASS_BRIDGE_PCI); + + sunxi_pcie_dbi_ro_wr_dis(pci); + + sunxi_pcie_host_rd_own_conf(pp, PCIE_LINK_WIDTH_SPEED_CONTROL, 4, &val); + val |= PORT_LOGIC_SPEED_CHANGE; + sunxi_pcie_host_wr_own_conf(pp, PCIE_LINK_WIDTH_SPEED_CONTROL, 4, val); +} +EXPORT_SYMBOL_GPL(sunxi_pcie_host_setup_rc); + +static int sunxi_pcie_host_wait_for_speed_change(struct sunxi_pcie *pci) +{ + u32 tmp; + unsigned int retries; + + for (retries = 0; retries < LINK_WAIT_MAX_RETRIE; retries++) { + tmp = sunxi_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL); + if (!(tmp & PORT_LOGIC_SPEED_CHANGE)) + return 0; + usleep_range(SPEED_CHANGE_USLEEP_MIN, SPEED_CHANGE_USLEEP_MAX); + } + + dev_err(pci->dev, "Speed change timeout\n"); + return -ETIMEDOUT; +} + +static int sunxi_pcie_host_read_speed(struct sunxi_pcie *pci) +{ + int val, gen; + + sunxi_pcie_dbi_ro_wr_en(pci); + val = sunxi_pcie_readl_dbi(pci, LINK_CONTROL2_LINK_STATUS2); + gen = val & 0xf; + + dev_info(pci->dev, "PCIe speed of Gen%d\n", gen); + + sunxi_pcie_dbi_ro_wr_dis(pci); + return 0; +} + +int sunxi_pcie_host_speed_change(struct sunxi_pcie *pci, int gen) +{ + int val; + int ret; + + sunxi_pcie_dbi_ro_wr_en(pci); + val = sunxi_pcie_readl_dbi(pci, LINK_CONTROL2_LINK_STATUS2); + val &= ~0xf; + val |= gen; + sunxi_pcie_writel_dbi(pci, LINK_CONTROL2_LINK_STATUS2, val); + + val = sunxi_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL); + val &= ~PORT_LOGIC_SPEED_CHANGE; + sunxi_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val); + + val = sunxi_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL); + val |= PORT_LOGIC_SPEED_CHANGE; + sunxi_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val); + + ret = sunxi_pcie_host_wait_for_speed_change(pci); + if (!ret) { + dev_info(pci->dev, "PCIe speed of Gen%d\n", gen); + } + else + dev_info(pci->dev, "PCIe speed of Gen1\n"); + + sunxi_pcie_dbi_ro_wr_dis(pci); + return 0; +} + +static void __sunxi_pcie_host_init(struct sunxi_pcie_port *pp) +{ + struct sunxi_pcie *pci = to_sunxi_pcie_from_pp(pp); + + if (!sunxi_pcie_host_is_link_up(pp)) { + sunxi_pcie_plat_ltssm_disable(pci); + if (!IS_ERR(pci->rst_gpio)) { + gpiod_set_raw_value(pci->rst_gpio, 0); + msleep(100); + gpiod_set_raw_value(pci->rst_gpio, 1); + } + } else { + msleep(100); + } + + sunxi_pcie_host_setup_rc(pp); + + if (sunxi_pcie_host_is_link_up(pp)) { + dev_info(pci->dev, "pcie is already link up\n"); + + sunxi_pcie_host_read_speed(pci); + } else { + sunxi_pcie_host_establish_link(pci); + + sunxi_pcie_host_speed_change(pci, pci->link_gen); + } +} + +static bool sunxi_pcie_host_link_up_status(struct sunxi_pcie_port *pp) +{ + u32 val; + int ret; + struct sunxi_pcie *pcie = to_sunxi_pcie_from_pp(pp); + + val = sunxi_pcie_readl(pcie, PCIE_LINK_STAT); + + if ((val & RDLH_LINK_UP) && (val & SMLH_LINK_UP)) + ret = true; + else + ret = false; + + return ret; +} + +static struct sunxi_pcie_host_ops sunxi_pcie_host_ops = { + .is_link_up = sunxi_pcie_host_link_up_status, + .host_init = __sunxi_pcie_host_init, +}; + +static int sunxi_pcie_host_wait_for_link(struct sunxi_pcie_port *pp) +{ + int retries; + + for (retries = 0; retries < LINK_WAIT_MAX_RETRIE; retries++) { + if (sunxi_pcie_host_is_link_up(pp)) { + dev_info(pp->dev, "pcie link up success\n"); + return 0; + } + usleep_range(LINK_WAIT_USLEEP_MIN, LINK_WAIT_USLEEP_MAX); + } + + return -ETIMEDOUT; +} + +int sunxi_pcie_host_establish_link(struct sunxi_pcie *pci) +{ + struct sunxi_pcie_port *pp = &pci->pp; + + if (sunxi_pcie_host_is_link_up(pp)) { + dev_info(pci->dev, "pcie is already link up\n"); + return 0; + } + + sunxi_pcie_plat_ltssm_enable(pci); + + return sunxi_pcie_host_wait_for_link(pp); +} +EXPORT_SYMBOL_GPL(sunxi_pcie_host_establish_link); + +static irqreturn_t sunxi_pcie_host_msi_irq_handler(int irq, void *arg) +{ + struct sunxi_pcie_port *pp = (struct sunxi_pcie_port *)arg; + struct sunxi_pcie *pci = to_sunxi_pcie_from_pp(pp); + unsigned long val; + int i, pos; + u32 status; + irqreturn_t ret = IRQ_NONE; + + for (i = 0; i < MAX_MSI_CTRLS; i++) { + status = sunxi_pcie_readl_dbi(pci, PCIE_MSI_INTR_STATUS + (i * MSI_REG_CTRL_BLOCK_SIZE)); + + if (!status) + continue; + + ret = IRQ_HANDLED; + pos = 0; + val = status; + while ((pos = find_next_bit(&val, MAX_MSI_IRQS_PER_CTRL, pos)) != MAX_MSI_IRQS_PER_CTRL) { + + /* Clear MSI interrupt first here. Otherwise some irqs will be lost or timeout */ + sunxi_pcie_writel_dbi(pci, + PCIE_MSI_INTR_STATUS + (i * MSI_REG_CTRL_BLOCK_SIZE), 1 << pos); + + generic_handle_domain_irq(pp->irq_domain, (i * MAX_MSI_IRQS_PER_CTRL) + pos); + pos++; + } + } + + return ret; +} + +int sunxi_pcie_host_add_port(struct sunxi_pcie *pci, struct platform_device *pdev) +{ + struct sunxi_pcie_port *pp = &pci->pp; + int ret; + + ret = of_property_read_u32(pp->dev->of_node, "num-ob-windows", &pp->num_ob_windows); + if (ret) { + dev_err(&pdev->dev, "failed to parse num-ob-windows\n"); + return -EINVAL; + } + + pp->has_its = device_property_read_bool(&pdev->dev, "msi-map"); + + if (pci_msi_enabled() && !pp->has_its) { + pp->msi_irq = platform_get_irq_byname(pdev, "msi"); + if (pp->msi_irq < 0) + return pp->msi_irq; + + ret = devm_request_irq(&pdev->dev, pp->msi_irq, sunxi_pcie_host_msi_irq_handler, + IRQF_SHARED, "pcie-msi", pp); + if (ret) { + dev_err(&pdev->dev, "failed to request MSI IRQ\n"); + return ret; + } + } + + pp->ops = &sunxi_pcie_host_ops; + raw_spin_lock_init(&pp->lock); + + ret = sunxi_pcie_host_init(pp); + if (ret) { + dev_err(&pdev->dev, "failed to initialize host\n"); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(sunxi_pcie_host_add_port); + +void sunxi_pcie_host_remove_port(struct sunxi_pcie *pci) +{ + struct sunxi_pcie_port *pp = &pci->pp; + + if (pp->bridge->bus) { + pci_stop_root_bus(pp->bridge->bus); + pci_remove_root_bus(pp->bridge->bus); + } + + if (pci_msi_enabled() && !pp->has_its) { + sunxi_pcie_free_msi(pp); + sunxi_free_msi_domains(pp); + } + sunxi_free_intx_domains(pp); +} +EXPORT_SYMBOL_GPL(sunxi_pcie_host_remove_port); diff --git a/drivers/pci/controller/allwinner/pcie-sunxi.h b/drivers/pci/controller/allwinner/pcie-sunxi.h new file mode 100644 index 00000000000000..08a2cac2ab1b82 --- /dev/null +++ b/drivers/pci/controller/allwinner/pcie-sunxi.h @@ -0,0 +1,401 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ +/* + * Allwinner PCIe controller driver + * + * Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. + * + * Author: songjundong + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef _PCIE_SUNXI_H +#define _PCIE_SUNXI_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pcie-sunxi-dma.h" + +#define PCIE_PORT_LINK_CONTROL 0x710 +#define PORT_LINK_MODE_MASK (0x3f << 16) +#define PORT_LINK_MODE_1_LANES (0x1 << 16) +#define PORT_LINK_MODE_2_LANES (0x3 << 16) +#define PORT_LINK_MODE_4_LANES (0x7 << 16) +#define PORT_LINK_LPBK_ENABLE (0x1 << 2) + +#define PCIE_LINK_WIDTH_SPEED_CONTROL 0x80C +#define PORT_LOGIC_SPEED_CHANGE (0x1 << 17) +#define PORT_LOGIC_LINK_WIDTH_MASK (0x1ff << 8) +#define PORT_LOGIC_LINK_WIDTH_1_LANES (0x1 << 8) +#define PORT_LOGIC_LINK_WIDTH_2_LANES (0x2 << 8) +#define PORT_LOGIC_LINK_WIDTH_4_LANES (0x4 << 8) + +#define PCIE_ATU_VIEWPORT 0x900 +#define PCIE_ATU_REGION_INBOUND (0x1 << 31) +#define PCIE_ATU_REGION_OUTBOUND (0x0 << 31) +#define PCIE_ATU_REGION_INDEX2 (0x2 << 0) +#define PCIE_ATU_REGION_INDEX1 (0x1 << 0) +#define PCIE_ATU_REGION_INDEX0 (0x0 << 0) + +#define PCIE_ATU_INDEX0 0x0 +#define PCIE_ATU_INDEX1 0x1 +#define PCIE_ATU_INDEX2 0x2 +#define PCIE_ATU_INDEX3 0x3 +#define PCIE_ATU_INDEX4 0x4 +#define PCIE_ATU_INDEX5 0x5 +#define PCIE_ATU_INDEX6 0x6 +#define PCIE_ATU_INDEX7 0x7 + +#define PCIE_EP_REBAR_SIZE_32M 0x200 + +#define PCIE_ATU_CR1_OUTBOUND(reg) (0x300000 + ((reg) * 0x200)) +#define PCIE_ATU_TYPE_MEM (0x0 << 0) +#define PCIE_ATU_TYPE_IO (0x2 << 0) +#define PCIE_ATU_TYPE_CFG0 (0x4 << 0) +#define PCIE_ATU_TYPE_CFG1 (0x5 << 0) +#define PCIE_ATU_CR2_OUTBOUND(reg) (0x300004 + ((reg) * 0x200)) +#define PCIE_ATU_DMA_BYPASS BIT(27) +#define PCIE_ATU_BAR_MODE_ENABLE BIT(30) +#define PCIE_ATU_ENABLE BIT(31) + +#define PCIE_ATU_LOWER_BASE_OUTBOUND(reg) (0x300008 + ((reg) * 0x200)) +#define PCIE_ATU_UPPER_BASE_OUTBOUND(reg) (0x30000c + ((reg) * 0x200)) +#define PCIE_ATU_LIMIT_OUTBOUND(reg) (0x300010 + ((reg) * 0x200)) +#define PCIE_ATU_LOWER_TARGET_OUTBOUND(reg) (0x300014 + ((reg) * 0x200)) +#define PCIE_ATU_UPPER_TARGET_OUTBOUND(reg) (0x300018 + ((reg) * 0x200)) + +#define PCIE_ATU_CR1_INBOUND(reg) (0x300100 + ((reg) * 0x200)) +#define PCIE_ATU_TYPE_MEM (0x0 << 0) +#define PCIE_ATU_TYPE_IO (0x2 << 0) +#define PCIE_ATU_TYPE_CFG0 (0x4 << 0) +#define PCIE_ATU_TYPE_CFG1 (0x5 << 0) +#define PCIE_ATU_FUNC_NUM(pf) ((pf) << 20) +#define PCIE_ATU_CR2_INBOUND(reg) (0x300104 + ((reg) * 0x200)) +#define PCIE_ATU_MATCH_MODE BIT(30) +#define PCIE_ATU_FUNC_NUM_MATCH_EN BIT(19) +#define PCIE_ATU_FUNC_NUM(pf) ((pf) << 20) + +#define PCIE_ATU_LOWER_BASE_INBOUND(reg) (0x300108 + ((reg) * 0x200)) +#define PCIE_ATU_UPPER_BASE_INBOUND(reg) (0x30010c + ((reg) * 0x200)) +#define PCIE_ATU_LIMIT_INBOUND(reg) (0x300110 + ((reg) * 0x200)) +#define PCIE_ATU_LOWER_TARGET_INBOUND(reg) (0x300114 + ((reg) * 0x200)) +#define PCIE_ATU_UPPER_TARGET_INBOUND(reg) (0x300118 + ((reg) * 0x200)) + +#define PCIE_ATU_BUS(x) (((x) & 0xff) << 24) +#define PCIE_ATU_DEV(x) (((x) & 0x1f) << 19) +#define PCIE_ATU_FUNC(x) (((x) & 0x7) << 16) + +#define PCIE_MISC_CONTROL_1_CFG 0x8bc +#define PCIE_TYPE1_CLASS_CODE_REV_ID_REG 0x08 + +#define PCIE_ADDRESS_ALIGNING (~0x3) +#define PCIE_HIGH_16 16 +#define PCIE_BAR_NUM 6 +#define PCIE_MEM_FLAGS 0x4 +#define PCIE_IO_FLAGS 0x1 +#define PCIE_BAR_REG 0x4 +#define PCIE_HIGH16_MASK 0xffff0000 +#define PCIE_LOW16_MASK 0x0000ffff +#define PCIE_INTERRUPT_LINE_MASK 0xffff00ff +#define PCIE_INTERRUPT_LINE_ENABLE 0x00000100 +#define PCIE_PRIMARY_BUS_MASK 0xff000000 +#define PCIE_PRIMARY_BUS_ENABLE 0x00010100 +#define PCIE_MEMORY_MASK 0xfff00000 + +#define PCIE_CPU_BASE 0x20000000 + +#define PCIE_TYPE0_STATUS_COMMAND_REG 0x4 + +#define PCIE_DBI2_BASE 0x100000 +#define DBI2_FUNC_OFFSET 0x10000 +#define BAR_ENABLE 0x1 + +#define RESBAR_CAP_REG 0x4 /* from PCIe spec4.0 7.8.6 */ +#define RESBAR_SIZE_MASK 0xfffff0 +#define RESBAR_CTL_REG 0x8 +#define RESBAR_NEXT_BAR 0x8 +#define SIZE_OF_1MB 20 /* 2^20 = 0x100000 */ + +#define PCIE_COMBO_PHY_BGR 0x04 +#define PHY_ACLK_EN BIT(17) +#define PHY_HCLK_EN BIT(16) +#define PHY_TERSTN BIT(1) +#define PHY_PW_UP_RSTN BIT(0) +#define PCIE_COMBO_PHY_CTL 0x10 +#define PHY_USE_SEL BIT(31) /* 0:PCIE; 1:USB3 */ +#define PHY_CLK_SEL BIT(30) /* 0:internal clk; 1:exteral clk */ +#define PHY_BIST_EN BIT(16) +#define PHY_PIPE_SW BIT(9) +#define PHY_PIPE_SEL BIT(8) /* 0:PIPE resetn ctrl by PCIE ctrl; 1:PIPE resetn ctrl by */ +#define PHY_PIPE_CLK_INVERT BIT(4) +#define PHY_FPGA_SYS_RSTN BIT(1) /* for PFGA */ +#define PHY_RSTN BIT(0) + +#define NEXT_CAP_PTR_MASK 0xff00 +#define CAP_ID_MASK 0x00ff + +/* + * Maximum number of MSI IRQs can be 256 per controller. But keep + * it 32 as of now. Probably we will never need more than 32. If needed, + * then increment it in multiple of 32. + */ +#define INT_PCI_MSI_NR 32 +#define MAX_MSI_IRQS 256 +#define MAX_MSI_IRQS_PER_CTRL 32 +#define MAX_MSI_CTRLS (MAX_MSI_IRQS / MAX_MSI_IRQS_PER_CTRL) +#define MSI_REG_CTRL_BLOCK_SIZE 12 + +/* #define MAX_MSI_IRQS 32 */ +/* #define MAX_MSI_CTRLS (MAX_MSI_IRQS / 32) */ +#define PCIE_LINK_WIDTH_SPEED_CONTROL 0x80C +#define PORT_LOGIC_SPEED_CHANGE (0x1 << 17) +#define LINK_CONTROL2_LINK_STATUS2 0xa0 +/* Parameters for the waiting for link up routine */ +#define LINK_WAIT_MAX_RETRIE 20 +#define LINK_WAIT_USLEEP_MIN 90000 +#define LINK_WAIT_USLEEP_MAX 100000 +#define SPEED_CHANGE_USLEEP_MIN 100 +#define SPEED_CHANGE_USLEEP_MAX 1000 +#define WAIT_ATU 1 + +#define PCIE_MSI_ADDR_LO 0x820 +#define PCIE_MSI_ADDR_HI 0x824 +#define PCIE_MSI_INTR_ENABLE(reg) (0x828 + ((reg) * 0x0c)) +/* #define PCIE_MSI_INTR_MASK(reg) (0x82C + ((reg) * 0x0c)) */ +/* #define PCIE_MSI_INTR_STATUS(reg) (0x830 + ((reg) * 0x0c)) */ +/* #define PCIE_MSI_INTR_ENABLE 0x828 */ +#define PCIE_MSI_INTR_MASK 0x82C +#define PCIE_MSI_INTR_STATUS 0x830 + +#define PCIE_CTRL_MGMT_BASE 0x900000 + +#define PCIE_USER_DEFINED_REGISTER 0x400000 +#define PCIE_VER 0x00 +#define PCIE_ADDR_PAGE_CFG 0x04 +#define PCIE_AWMISC_CTRL 0x200 +#define PCIE_ARMISC_CTRL 0x220 +#define PCIE_LTSSM_CTRL 0xc00 +#define PCIE_LINK_TRAINING BIT(0) /* 0:disable; 1:enable */ +#define DEVICE_TYPE_MASK GENMASK(7, 4) +#define DEVICE_TYPE_RC BIT(6) +#define PCIE_INT_ENABLE_CLR 0xE04 /* BIT(1):RDLH_LINK_MASK; BIT(0):SMLH_LINK_MASK */ +#define PCIE_LINK_STAT 0xE0C /* BIT(1):RDLH_LINK; BIT(0):SMLH_LINK */ +#define RDLH_LINK_UP BIT(1) +#define SMLH_LINK_UP BIT(0) +#define PCIE_LINK_INT_EN (BIT(0) | BIT(1)) + +#define SII_INT_MASK0 0x0e00 +#define SII_INT_STAS0 0x0e08 + #define INTX_TX_DEASSERT_MASK GENMASK(28, 25) + #define INTX_TX_DEASSERT_SHIFT 25 + #define INTX_TX_DEASSERT(x) BIT((x) + INTX_TX_DEASSERT_SHIFT) + #define INTX_TX_ASSERT_MASK GENMASK(24, 21) + #define INTX_TX_ASSERT_SHIFT 21 + #define INTX_TX_ASSERT(x) BIT((x) + INTX_TX_ASSERT_SHIFT) + #define INTX_RX_DEASSERT_MASK GENMASK(12, 9) + #define INTX_RX_DEASSERT_SHIFT 9 + #define INTX_RX_DEASSERT(x) BIT((x) + INTX_RX_DEASSERT_SHIFT) + #define INTX_RX_ASSERT_MASK GENMASK(8, 5) + #define INTX_RX_ASSERT_SHIFT 5 + #define INTX_RX_ASSERT(x) BIT((x) + INTX_RX_ASSERT_SHIFT) + +#define PCIE_PHY_CFG 0x800 +#define SYS_CLK 0 +#define PAD_CLK 1 +#define PCIE_LINK_UP_MASK (0x3<<16) + +#define PCIE_RC_RP_ATS_BASE 0x400000 + +#define SUNXI_PCIE_BAR_CFG_CTRL_DISABLED 0x0 +#define SUNXI_PCIE_BAR_CFG_CTRL_IO_32BITS 0x1 +#define SUNXI_PCIE_BAR_CFG_CTRL_MEM_32BITS 0x4 +#define SUNXI_PCIE_BAR_CFG_CTRL_PREFETCH_MEM_32BITS 0x5 +#define SUNXI_PCIE_BAR_CFG_CTRL_MEM_64BITS 0x6 +#define SUNXI_PCIE_BAR_CFG_CTRL_PREFETCH_MEM_64BITS 0x7 + +#define SUNXI_PCIE_EP_MSI_CTRL_REG 0x90 +#define SUNXI_PCIE_EP_MSI_CTRL_MMC_OFFSET 17 +#define SUNXI_PCIE_EP_MSI_CTRL_MMC_MASK GENMASK(19, 17) +#define SUNXI_PCIE_EP_MSI_CTRL_MME_OFFSET 20 +#define SUNXI_PCIE_EP_MSI_CTRL_MME_MASK GENMASK(22, 20) +#define SUNXI_PCIE_EP_MSI_CTRL_ME BIT(16) +#define SUNXI_PCIE_EP_MSI_CTRL_MASK_MSI_CAP BIT(24) +#define SUNXI_PCIE_EP_DUMMY_IRQ_ADDR 0x1 + +#define PCIE_PHY_FUNC_CFG (PCIE_CTRL_MGMT_BASE + 0x2c0) +#define PCIE_RC_BAR_CONF (PCIE_CTRL_MGMT_BASE + 0x300) + +enum sunxi_pcie_device_mode { + SUNXI_PCIE_EP_TYPE, + SUNXI_PCIE_RC_TYPE, +}; + +struct sunxi_pcie_of_data { + const struct sunxi_pcie_ep_ops *ops; + enum sunxi_pcie_device_mode mode; + u32 func_offset; + bool cpu_pcie_addr_quirk; + bool has_pcie_slv_clk; + bool need_pcie_rst; + bool pcie_slv_clk_400m; + bool has_pcie_its_clk; +}; + +struct sunxi_pcie_ep_func { + struct list_head list; + u8 func_no; + u8 msi_cap; + u8 msix_cap; +}; + +struct sunxi_pcie_ep { + struct pci_epc *epc; + struct list_head func_list; + const struct sunxi_pcie_ep_ops *ops; + phys_addr_t phys_base; + size_t addr_size; + size_t page_size; + u8 bar_to_atu[PCI_STD_NUM_BARS]; + phys_addr_t *outbound_addr; + u32 num_ib_windows; + u32 num_ob_windows; + unsigned long *ib_window_map; + unsigned long *ob_window_map; + void __iomem *msi_mem; + phys_addr_t msi_mem_phys; + struct pci_epf_bar *epf_bar[PCI_STD_NUM_BARS]; +}; + +struct sunxi_pcie_ep_ops { + void (*ep_init)(struct sunxi_pcie_ep *ep); + int (*raise_irq)(struct sunxi_pcie_ep *ep, u8 func_no, + unsigned int type, u16 interrupt_num); + const struct pci_epc_features *(*get_features)(struct sunxi_pcie_ep *ep); + unsigned int (*func_conf_select)(struct sunxi_pcie_ep *ep, u8 func_no); +}; + +struct sunxi_pcie_port { + struct device *dev; + void __iomem *dbi_base; + u64 cfg0_base; + void __iomem *va_cfg0_base; + u32 cfg0_size; + resource_size_t io_base; + phys_addr_t io_bus_addr; + u32 io_size; + u32 num_ob_windows; + struct sunxi_pcie_host_ops *ops; + int msi_irq; + struct irq_domain *intx_domain; + struct irq_domain *irq_domain; + struct irq_domain *msi_domain; + u16 msi_msg; + dma_addr_t msi_data; + struct pci_host_bridge *bridge; + raw_spinlock_t lock; + unsigned long msi_map[BITS_TO_LONGS(INT_PCI_MSI_NR)]; + bool has_its; + bool cpu_pcie_addr_quirk; +}; + +struct sunxi_pci_edma_chan; + +struct sunxi_pcie { + struct device *dev; + void __iomem *dbi_base; + void __iomem *app_base; + int link_gen; + struct sunxi_pcie_port pp; + struct sunxi_pcie_ep ep; + struct clk *pcie_aux; + struct clk *pcie_slv; + struct clk *pcie_its; + struct reset_control *pcie_rst; + struct reset_control *pwrup_rst; + struct reset_control *pcie_its_rst; + struct phy *phy; + struct dma_trx_obj *dma_obj; + const struct sunxi_pcie_of_data *drvdata; + struct gpio_desc *rst_gpio; + struct gpio_desc *wake_gpio; + u32 lanes; + u32 num_edma; + unsigned long *rd_edma_map; + unsigned long *wr_edma_map; + struct sunxi_pci_edma_chan *dma_wr_chn; + struct sunxi_pci_edma_chan *dma_rd_chn; + struct regulator *pcie1v8; + struct regulator *pcie3v3; +}; + +#define to_sunxi_pcie_from_pp(x) \ + container_of((x), struct sunxi_pcie, pp) + +#define to_sunxi_pcie_from_ep(endpoint) \ + container_of((endpoint), struct sunxi_pcie, ep) + +struct sunxi_pcie_host_ops { + void (*readl_rc)(struct sunxi_pcie_port *pp, void __iomem *dbi_base, u32 *val); + void (*writel_rc)(struct sunxi_pcie_port *pp, u32 val, void __iomem *dbi_base); + int (*rd_own_conf)(struct sunxi_pcie_port *pp, int where, int size, u32 *val); + int (*wr_own_conf)(struct sunxi_pcie_port *pp, int where, int size, u32 val); + bool (*is_link_up)(struct sunxi_pcie_port *pp); + void (*host_init)(struct sunxi_pcie_port *pp); + void (*scan_bus)(struct sunxi_pcie_port *pp); +}; + +void sunxi_pcie_plat_set_rate(struct sunxi_pcie *pci); +void sunxi_pcie_write_dbi(struct sunxi_pcie *pci, u32 reg, size_t size, u32 val); +u32 sunxi_pcie_read_dbi(struct sunxi_pcie *pci, u32 reg, size_t size); +void sunxi_pcie_plat_ltssm_enable(struct sunxi_pcie *pci); +void sunxi_pcie_plat_ltssm_disable(struct sunxi_pcie *pci); +int sunxi_pcie_cfg_write(void __iomem *addr, int size, u32 val); +int sunxi_pcie_cfg_read(void __iomem *addr, int size, u32 *val); + +#if IS_ENABLED(CONFIG_PCIE_SUNXI_RC) +int sunxi_pcie_host_add_port(struct sunxi_pcie *pci, struct platform_device *pdev); +void sunxi_pcie_host_remove_port(struct sunxi_pcie *pci); +int sunxi_pcie_host_speed_change(struct sunxi_pcie *pci, int gen); +int sunxi_pcie_host_wr_own_conf(struct sunxi_pcie_port *pp, int where, int size, u32 val); +int sunxi_pcie_host_establish_link(struct sunxi_pcie *pci); +void sunxi_pcie_host_setup_rc(struct sunxi_pcie_port *pp); +#else +static inline int sunxi_pcie_host_add_port(struct sunxi_pcie *pci, struct platform_device *pdev) {return 0; } +static inline void sunxi_pcie_host_remove_port(struct sunxi_pcie *pci) {} +static inline int sunxi_pcie_host_speed_change(struct sunxi_pcie *pci, int gen) {return 0; } +static inline int sunxi_pcie_host_wr_own_conf(struct sunxi_pcie_port *pp, int where, int size, u32 val) {return 0; } +static inline int sunxi_pcie_host_establish_link(struct sunxi_pcie *pci) {return 0; } +static inline void sunxi_pcie_host_setup_rc(struct sunxi_pcie_port *pp) {} +#endif + +#if IS_ENABLED(CONFIG_PCIE_SUNXI_EP) +int sunxi_pcie_ep_init(struct sunxi_pcie *pci); +void sunxi_pcie_ep_deinit(struct sunxi_pcie *pci); +#else +static inline int sunxi_pcie_ep_init(struct sunxi_pcie *pci) {return 0; } +static inline void sunxi_pcie_ep_deinit(struct sunxi_pcie *pci) {} +#endif + +void sunxi_pcie_writel(u32 val, struct sunxi_pcie *pcie, u32 offset); +u32 sunxi_pcie_readl(struct sunxi_pcie *pcie, u32 offset); +void sunxi_pcie_writel_dbi(struct sunxi_pcie *pci, u32 reg, u32 val); +u32 sunxi_pcie_readl_dbi(struct sunxi_pcie *pci, u32 reg); +void sunxi_pcie_writew_dbi(struct sunxi_pcie *pci, u32 reg, u16 val); +u16 sunxi_pcie_readw_dbi(struct sunxi_pcie *pci, u32 reg); +void sunxi_pcie_writeb_dbi(struct sunxi_pcie *pci, u32 reg, u8 val); +u8 sunxi_pcie_readb_dbi(struct sunxi_pcie *pci, u32 reg); +void sunxi_pcie_dbi_ro_wr_en(struct sunxi_pcie *pci); +void sunxi_pcie_dbi_ro_wr_dis(struct sunxi_pcie *pci); + +#endif /* _PCIE_SUNXI_H */ From 89c815fed169c4699f46d539c9be4fc832c3ee7b Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Mon, 3 Aug 2026 20:49:03 +0000 Subject: [PATCH 4/9] arm64: dts: allwinner: Describe Combo PHY for A733 Signed-off-by: Yao Zi --- .../arm64/boot/dts/allwinner/sun60i-a733.dtsi | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/arch/arm64/boot/dts/allwinner/sun60i-a733.dtsi b/arch/arm64/boot/dts/allwinner/sun60i-a733.dtsi index 10bf64c730fdf9..be7f7c37e9c49e 100644 --- a/arch/arm64/boot/dts/allwinner/sun60i-a733.dtsi +++ b/arch/arm64/boot/dts/allwinner/sun60i-a733.dtsi @@ -774,6 +774,64 @@ }; }; + serdes: serdes@6c00000 { + compatible = "allwinner,cadence-combophy"; + reg = <0x0 0x06c00000 0x0 0x400>, + <0x0 0x06c06000 0x0 0x2000>, + <0x0 0x0709016c 0x0 0x4>; + clocks = <&ccu CLK_SERDES_PHY_CFG>, + <&rtc_ccu CLK_RTC_DCXO_SERDES0>, + <&rtc_ccu CLK_RTC_DCXO_SERDES1>; + clock-names = "serdes-clk", + "dcxo-serdes0-clk", + "dcxo-serdes1-clk"; + ranges; + resets = <&ccu RST_BUS_SERDES>; + reset-names = "serdes-reset"; + #address-cells = <2>; + #size-cells = <2>; + status = "disabled"; + + combophy0: combo-phy0@6c01000 { + reg = <0x0 0x06c01000 0x0 0xa00>, + <0x0 0x06c80000 0x0 0x20000>; + + combo0_dp: combo0-dp-phy { + #phy-cells = <0>; + status = "okay"; + }; + + combo0_usb: combo0-usb-phy { + #phy-cells = <0>; + status = "okay"; + }; + }; + + combophy1: combo-phy1@6c02000 { + reg = <0x0 0x06c02000 0x0 0xa00>, + <0x0 0x06ca0000 0x0 0x20000>; + + combo1_usb: combo1-usb-phy { + #phy-cells = <0>; + status = "okay"; + }; + + combo1_pcie: combo1-pcie-phy { + #phy-cells = <0>; + status = "okay"; + }; + }; + + aux_hpd: aux-hpd@6c01e00 { + reg = <0x0 0x06c01e00 0x0 0x200>; + + aux_hpd_phy: aux-hpd-phy { + #phy-cells = <0>; + status = "okay"; + }; + }; + }; + r_ccu: r_ccu@7010000 { compatible = "allwinner,sun60iw2-r-ccu"; reg = <0x0 0x07010000 0x0 0x340>; From 8742203bf467e5c812fa42907e2d4294efa7c05d Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Mon, 3 Aug 2026 20:49:32 +0000 Subject: [PATCH 5/9] arm64: dts: allwinner: Describe PCIe root complex for A733 Signed-off-by: Yao Zi --- .../arm64/boot/dts/allwinner/sun60i-a733.dtsi | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/arch/arm64/boot/dts/allwinner/sun60i-a733.dtsi b/arch/arm64/boot/dts/allwinner/sun60i-a733.dtsi index be7f7c37e9c49e..b61a26ea9774db 100644 --- a/arch/arm64/boot/dts/allwinner/sun60i-a733.dtsi +++ b/arch/arm64/boot/dts/allwinner/sun60i-a733.dtsi @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -774,6 +775,61 @@ }; }; + pcie_rc: pcie@6000000 { + compatible = "allwinner,sunxi-pcie-v300-rc"; + bus-range = <0x0 0xff>; + reg = <0 0x06000000 0 0x480000>; + reg-names = "dbi"; + device_type = "pci"; + clocks = <&ccu CLK_PCIE0_AUX>, + <&ccu CLK_PCIE0_AXI_SLV>, + <&ccu CLK_ITS_PCIE0_A>; + clock-names = "pclk_aux", "pclk_slv", "its"; + interrupts = , + , + , + , + , + , + , + , + , + ; + interrupt-names = "sii", "msi", "edma-w0", "edma-w1", "edma-w2", "edma-w3", + "edma-r0", "edma-r1", "edma-r2", "edma-r3"; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &pcie_intc 0>, + <0 0 0 2 &pcie_intc 1>, + <0 0 0 3 &pcie_intc 2>, + <0 0 0 4 &pcie_intc 3>; + linux,pci-domain = <0>; + max-link-speed = <3>; + num-edma = <4>; + num-lanes = <1>; + num-ib-windows = <16>; + num-ob-windows = <16>; + phys = <&combo1_pcie>; + phy-names = "pcie-phy"; + power-domains = <&pck600 PD_PCIE>; + ranges = <0x00000800 0 0x20000000 0x0 0x20000000 0 0x01000000 + 0x81000000 0 0x21000000 0x0 0x21000000 0 0x01000000 + 0x82000000 0 0x22000000 0x0 0x22000000 0 0x06000000>; + resets = <&ccu RST_BUS_PCIE0>, + <&ccu RST_BUS_PCIE0_PWRUP>, + <&ccu RST_BUS_ITS_PCIE0>; + reset-names = "pclk_rst", "pwrup_rst", "its"; + #address-cells = <3>; + #interrupt-cells = <1>; + #size-cells = <2>; + status = "disabled"; + + pcie_intc: legacy-interrupt-controller { + interrupt-controller; + #address-cells = <0>; + #interrupt-cells = <1>; + }; + }; + serdes: serdes@6c00000 { compatible = "allwinner,cadence-combophy"; reg = <0x0 0x06c00000 0x0 0x400>, From 9d0197f3e5f2417839c9cf2a2d210342bb770935 Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Tue, 11 Aug 2026 19:18:15 +0000 Subject: [PATCH 6/9] arm64: dts: allwinner: Enable PCIe RC on A733 Demo AIOT board Signed-off-by: Yao Zi --- .../boot/dts/allwinner/sun60i-a733-demo-aiot.dts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/arch/arm64/boot/dts/allwinner/sun60i-a733-demo-aiot.dts b/arch/arm64/boot/dts/allwinner/sun60i-a733-demo-aiot.dts index 8147493d9fb043..520087a9a13882 100644 --- a/arch/arm64/boot/dts/allwinner/sun60i-a733-demo-aiot.dts +++ b/arch/arm64/boot/dts/allwinner/sun60i-a733-demo-aiot.dts @@ -187,6 +187,13 @@ status = "okay"; }; +&pcie_rc { + reset-gpios = <&pio 8 10 GPIO_ACTIVE_HIGH>; + wake-gpios = <&pio 8 13 GPIO_ACTIVE_HIGH>; + pcie3v3-supply = <&swout2>; + status = "okay"; +}; + &pio { /* PF = SD, PB = UART0, PG = Wi-Fi (DCDC1 3.3V) */ vcc-pf-supply = <&swout2>; @@ -407,6 +414,13 @@ cpu-supply = <®_dcdc3>; }; +&serdes { + serdes1v8-supply = <&cldo5>; + avdd-c-supply = <®_dcdc2>; + avdd-d-supply = <®_dcdc2>; + status = "okay"; +}; + &uart0 { pinctrl-names = "default"; pinctrl-0 = <&uart0_pins>; From bf4f931b6aa1244345508cf983e60b86461150cb Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Mon, 27 Jul 2026 22:42:00 +0000 Subject: [PATCH 7/9] phy: sun4i-usb: Allow shared reset signal On Allwinner A733, the third USB 2.0 PHY and controller share a common reset line. Signed-off-by: Yao Zi --- drivers/phy/allwinner/phy-sun4i-usb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/allwinner/phy-sun4i-usb.c b/drivers/phy/allwinner/phy-sun4i-usb.c index 59d38d88efb085..14fc33165acb37 100644 --- a/drivers/phy/allwinner/phy-sun4i-usb.c +++ b/drivers/phy/allwinner/phy-sun4i-usb.c @@ -787,7 +787,7 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev) continue; snprintf(name, sizeof(name), "usb%d_reset", i); - phy->reset = devm_reset_control_get(dev, name); + phy->reset = devm_reset_control_get_shared(dev, name); if (IS_ERR(phy->reset)) { if (PTR_ERR(phy->reset) == -ENOENT) break; From 22de2ed3b26b94b244e1a313cf448b7100da3765 Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Mon, 27 Jul 2026 22:43:22 +0000 Subject: [PATCH 8/9] usb: dwc3: generic-plat: Support Allwinner A733 Signed-off-by: Yao Zi --- drivers/usb/dwc3/dwc3-generic-plat.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-generic-plat.c b/drivers/usb/dwc3/dwc3-generic-plat.c index f8ad79c08c4e5c..ffaa0ce5be5c27 100644 --- a/drivers/usb/dwc3/dwc3-generic-plat.c +++ b/drivers/usb/dwc3/dwc3-generic-plat.c @@ -47,16 +47,18 @@ static int dwc3_generic_probe(struct platform_device *pdev) return -ENODEV; } - dwc3g->resets = devm_reset_control_array_get_optional_exclusive(dev); + dwc3g->resets = devm_reset_control_array_get_optional_shared(dev); if (IS_ERR(dwc3g->resets)) return dev_err_probe(dev, PTR_ERR(dwc3g->resets), "failed to get resets\n"); - ret = reset_control_assert(dwc3g->resets); - if (ret) - return dev_err_probe(dev, ret, "failed to assert resets\n"); + if (of_device_get_match_data(dev)) { + ret = reset_control_assert(dwc3g->resets); + if (ret) + return dev_err_probe(dev, ret, "failed to assert resets\n"); - /* Not strict timing, just for safety */ - udelay(2); + /* Not strict timing, just for safety */ + udelay(2); + } ret = reset_control_deassert(dwc3g->resets); if (ret) @@ -143,7 +145,8 @@ static const struct dev_pm_ops dwc3_generic_dev_pm_ops = { }; static const struct of_device_id dwc3_generic_of_match[] = { - { .compatible = "spacemit,k1-dwc3", }, + { .compatible = "spacemit,k1-dwc3", .data = (void *)true}, + { .compatible = "allwinner,sun60i-a733-dwc3", .data = (void*)false}, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, dwc3_generic_of_match); From c2d381faf5315538908c6e79ec7302bd63606d95 Mon Sep 17 00:00:00 2001 From: Yao Zi Date: Mon, 27 Jul 2026 22:48:47 +0000 Subject: [PATCH 9/9] arm64: dts: allwinner: Describe the DWC3 xHCI controller for A733 Signed-off-by: Yao Zi --- .../arm64/boot/dts/allwinner/sun60i-a733.dtsi | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/arch/arm64/boot/dts/allwinner/sun60i-a733.dtsi b/arch/arm64/boot/dts/allwinner/sun60i-a733.dtsi index b61a26ea9774db..36fd4e34f34d8c 100644 --- a/arch/arm64/boot/dts/allwinner/sun60i-a733.dtsi +++ b/arch/arm64/boot/dts/allwinner/sun60i-a733.dtsi @@ -888,6 +888,32 @@ }; }; + xhci: usb@6a00000 { + compatible = "allwinner,sun60i-a733-dwc3"; + reg = <0x0 0x6a00000 0x0 0x100000>; + dr_mode = "otg"; + clocks = <&ccu CLK_USB2_MF>, <&ccu CLK_USB2_U2_REF>, + <&ccu CLK_USB2_SUSPEND>, + <&ccu CLK_USB2_U3_UTMI>, <&ccu CLK_USB2_U2_PIPE>; + clock-names = "bus_clk", "ref_clk", "suspend", + "utmi", "pipe"; + resets = <&ccu RST_USB_2>; + assigned-clocks = <&ccu CLK_USB2_SUSPEND>, <&ccu CLK_USB2_MF>; + assigned-clock-rates = <24000000>, <300000000>; + interrupts = ; + maximum-speed = "super-speed-plus"; + phy_type = "utmi"; + power-domains = <&pck600 PD_USB2>; + snps,dis_enblslpm_quirk; + snps,dis-u1-entry-quirk; + snps,dis-u2-entry-quirk; + snps,dis_u2_susphy_quirk; + snps,dis_u3_susphy_quirk; + phys = <&usbphy 2>, <&combo1_usb>; + phy-names = "usb2-phy", "usb3-phy"; + status = "disabled"; + }; + r_ccu: r_ccu@7010000 { compatible = "allwinner,sun60iw2-r-ccu"; reg = <0x0 0x07010000 0x0 0x340>;