From 2f2ce8fe3c0f2f45f6c273a3b7854a72e279005c Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Mon, 6 Aug 2018 22:14:12 -0700 Subject: [PATCH 01/41] dt-bindings: phy: qcom-qmp: Cleanup the 'reg' documentation as per review After the commit 8b1087fa3a27 ("phy: qcom-qmp: Fix dts bindings to reflect reality") landed there was some review feedback that 'reg' should have been documented differently. Fix it as per review feedback. As per that feedback: - Subject should have been 'dt-bindings: phy:' which this patch now has. - We should leave no ambiguity in the ordering of 'reg' ranges even if 'reg-names' are also specified. - Normally using reg-names is discouraged unless there's a strong reason it's needed (like if there are optional ranges). In this case reg-names wasn't needed but the driver already landed relying on reg-names so we'll just document it and move on. Fixes: 8b1087fa3a27 ("phy: qcom-qmp: Fix dts bindings to reflect reality") Suggested-by: Rob Herring Signed-off-by: Douglas Anderson Reviewed-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- .../devicetree/bindings/phy/qcom-qmp-phy.txt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt b/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt index 0c7629e88bf3a..2b161bf15d5d8 100644 --- a/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt +++ b/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt @@ -12,14 +12,17 @@ Required properties: "qcom,sdm845-qmp-usb3-phy" for USB3 QMP V3 phy on sdm845, "qcom,sdm845-qmp-usb3-uni-phy" for USB3 QMP V3 UNI phy on sdm845. - - reg: - - For "qcom,sdm845-qmp-usb3-phy": - - index 0: address and length of register set for PHY's common serdes - block. - - named register "dp_com" (using reg-names): address and length of the - DP_COM control block. - - For all others: - - offset and length of register set for PHY's common serdes block. +- reg: + - index 0: address and length of register set for PHY's common + serdes block. + - index 1: address and length of the DP_COM control block (for + "qcom,sdm845-qmp-usb3-phy" only). + +- reg-names: + - For "qcom,sdm845-qmp-usb3-phy": + - Should be: "reg-base", "dp_com" + - For all others: + - The reg-names property shouldn't be defined. - #clock-cells: must be 1 - Phy pll outputs a bunch of clocks for Tx, Rx and Pipe From 7effc8ba3e831b740b792093be8765290e49c0c8 Mon Sep 17 00:00:00 2001 From: Scott Telford Date: Thu, 9 Aug 2018 11:30:29 +0100 Subject: [PATCH 02/41] dt-bindings: phy: Document Cadence MHDP DisplayPort PHY bindings Signed-off-by: Scott Telford Signed-off-by: Kishon Vijay Abraham I --- .../bindings/phy/phy-cadence-dp.txt | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/phy-cadence-dp.txt diff --git a/Documentation/devicetree/bindings/phy/phy-cadence-dp.txt b/Documentation/devicetree/bindings/phy/phy-cadence-dp.txt new file mode 100644 index 0000000000000..7f49fd54ebc1f --- /dev/null +++ b/Documentation/devicetree/bindings/phy/phy-cadence-dp.txt @@ -0,0 +1,30 @@ +Cadence MHDP DisplayPort SD0801 PHY binding +=========================================== + +This binding describes the Cadence SD0801 PHY hardware included with +the Cadence MHDP DisplayPort controller. + +------------------------------------------------------------------------------- +Required properties (controller (parent) node): +- compatible : Should be "cdns,dp-phy" +- reg : Defines the following sets of registers in the parent + mhdp device: + - Offset of the DPTX PHY configuration registers + - Offset of the SD0801 PHY configuration registers +- #phy-cells : from the generic PHY bindings, must be 0. + +Optional properties: +- num_lanes : Number of DisplayPort lanes to use (1, 2 or 4) +- max_bit_rate : Maximum DisplayPort link bit rate to use, in Mbps (2160, + 2430, 2700, 3240, 4320, 5400 or 8100) +------------------------------------------------------------------------------- + +Example: + dp_phy: phy@f0fb030a00 { + compatible = "cdns,dp-phy"; + reg = <0xf0 0xfb030a00 0x0 0x00000040>, + <0xf0 0xfb500000 0x0 0x00100000>; + num_lanes = <4>; + max_bit_rate = <8100>; + #phy-cells = <0>; + }; From c8b427edc7378fa540a03d44ed61eb49cb7f64bc Mon Sep 17 00:00:00 2001 From: Scott Telford Date: Thu, 9 Aug 2018 11:30:30 +0100 Subject: [PATCH 03/41] phy: Add driver for Cadence MHDP DisplayPort SD0801 PHY Add driver for the Cadence SD0801 "Torrent" PHY used with the Cadence MHDP DisplayPort Tx controller. Integration with the MHDP driver will be the subject of another commit. Signed-off-by: Scott Telford Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/Kconfig | 1 + drivers/phy/Makefile | 1 + drivers/phy/cadence/Kconfig | 10 + drivers/phy/cadence/Makefile | 1 + drivers/phy/cadence/phy-cadence-dp.c | 541 +++++++++++++++++++++++++++ 5 files changed, 554 insertions(+) create mode 100644 drivers/phy/cadence/Kconfig create mode 100644 drivers/phy/cadence/Makefile create mode 100644 drivers/phy/cadence/phy-cadence-dp.c diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 5c8d452e35e2e..cc47f85088501 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -43,6 +43,7 @@ config PHY_XGENE source "drivers/phy/allwinner/Kconfig" source "drivers/phy/amlogic/Kconfig" source "drivers/phy/broadcom/Kconfig" +source "drivers/phy/cadence/Kconfig" source "drivers/phy/hisilicon/Kconfig" source "drivers/phy/lantiq/Kconfig" source "drivers/phy/marvell/Kconfig" diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 84e3bd9c5665e..ba48acdd9ed16 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_ARCH_RENESAS) += renesas/ obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/ obj-$(CONFIG_ARCH_TEGRA) += tegra/ obj-y += broadcom/ \ + cadence/ \ hisilicon/ \ marvell/ \ motorola/ \ diff --git a/drivers/phy/cadence/Kconfig b/drivers/phy/cadence/Kconfig new file mode 100644 index 0000000000000..57fff7de4031d --- /dev/null +++ b/drivers/phy/cadence/Kconfig @@ -0,0 +1,10 @@ +# +# Phy driver for Cadence MHDP DisplayPort controller +# +config PHY_CADENCE_DP + tristate "Cadence MHDP DisplayPort PHY driver" + depends on OF + depends on HAS_IOMEM + select GENERIC_PHY + help + Support for Cadence MHDP DisplayPort PHY. diff --git a/drivers/phy/cadence/Makefile b/drivers/phy/cadence/Makefile new file mode 100644 index 0000000000000..e5b0a11cf28a6 --- /dev/null +++ b/drivers/phy/cadence/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_PHY_CADENCE_DP) += phy-cadence-dp.o diff --git a/drivers/phy/cadence/phy-cadence-dp.c b/drivers/phy/cadence/phy-cadence-dp.c new file mode 100644 index 0000000000000..bc10cb264b7ae --- /dev/null +++ b/drivers/phy/cadence/phy-cadence-dp.c @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Cadence MHDP DisplayPort SD0801 PHY driver. + * + * Copyright 2018 Cadence Design Systems, Inc. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_NUM_LANES 2 +#define MAX_NUM_LANES 4 +#define DEFAULT_MAX_BIT_RATE 8100 /* in Mbps */ + +#define POLL_TIMEOUT_US 2000 +#define LANE_MASK 0x7 + +/* + * register offsets from DPTX PHY register block base (i.e MHDP + * register base + 0x30a00) + */ +#define PHY_AUX_CONFIG 0x00 +#define PHY_AUX_CTRL 0x04 +#define PHY_RESET 0x20 +#define PHY_PMA_XCVR_PLLCLK_EN 0x24 +#define PHY_PMA_XCVR_PLLCLK_EN_ACK 0x28 +#define PHY_PMA_XCVR_POWER_STATE_REQ 0x2c +#define PHY_POWER_STATE_LN_0 0x0000 +#define PHY_POWER_STATE_LN_1 0x0008 +#define PHY_POWER_STATE_LN_2 0x0010 +#define PHY_POWER_STATE_LN_3 0x0018 +#define PHY_PMA_XCVR_POWER_STATE_ACK 0x30 +#define PHY_PMA_CMN_READY 0x34 +#define PHY_PMA_XCVR_TX_VMARGIN 0x38 +#define PHY_PMA_XCVR_TX_DEEMPH 0x3c + +/* + * register offsets from SD0801 PHY register block base (i.e MHDP + * register base + 0x500000) + */ +#define CMN_SSM_BANDGAP_TMR 0x00084 +#define CMN_SSM_BIAS_TMR 0x00088 +#define CMN_PLLSM0_PLLPRE_TMR 0x000a8 +#define CMN_PLLSM0_PLLLOCK_TMR 0x000b0 +#define CMN_PLLSM1_PLLPRE_TMR 0x000c8 +#define CMN_PLLSM1_PLLLOCK_TMR 0x000d0 +#define CMN_BGCAL_INIT_TMR 0x00190 +#define CMN_BGCAL_ITER_TMR 0x00194 +#define CMN_IBCAL_INIT_TMR 0x001d0 +#define CMN_PLL0_VCOCAL_INIT_TMR 0x00210 +#define CMN_PLL0_VCOCAL_ITER_TMR 0x00214 +#define CMN_PLL0_VCOCAL_REFTIM_START 0x00218 +#define CMN_PLL0_VCOCAL_PLLCNT_START 0x00220 +#define CMN_PLL0_INTDIV_M0 0x00240 +#define CMN_PLL0_FRACDIVL_M0 0x00244 +#define CMN_PLL0_FRACDIVH_M0 0x00248 +#define CMN_PLL0_HIGH_THR_M0 0x0024c +#define CMN_PLL0_DSM_DIAG_M0 0x00250 +#define CMN_PLL0_LOCK_PLLCNT_START 0x00278 +#define CMN_PLL1_VCOCAL_INIT_TMR 0x00310 +#define CMN_PLL1_VCOCAL_ITER_TMR 0x00314 +#define CMN_PLL1_DSM_DIAG_M0 0x00350 +#define CMN_TXPUCAL_INIT_TMR 0x00410 +#define CMN_TXPUCAL_ITER_TMR 0x00414 +#define CMN_TXPDCAL_INIT_TMR 0x00430 +#define CMN_TXPDCAL_ITER_TMR 0x00434 +#define CMN_RXCAL_INIT_TMR 0x00450 +#define CMN_RXCAL_ITER_TMR 0x00454 +#define CMN_SD_CAL_INIT_TMR 0x00490 +#define CMN_SD_CAL_ITER_TMR 0x00494 +#define CMN_SD_CAL_REFTIM_START 0x00498 +#define CMN_SD_CAL_PLLCNT_START 0x004a0 +#define CMN_PDIAG_PLL0_CTRL_M0 0x00680 +#define CMN_PDIAG_PLL0_CLK_SEL_M0 0x00684 +#define CMN_PDIAG_PLL0_CP_PADJ_M0 0x00690 +#define CMN_PDIAG_PLL0_CP_IADJ_M0 0x00694 +#define CMN_PDIAG_PLL0_FILT_PADJ_M0 0x00698 +#define CMN_PDIAG_PLL0_CP_PADJ_M1 0x006d0 +#define CMN_PDIAG_PLL0_CP_IADJ_M1 0x006d4 +#define CMN_PDIAG_PLL1_CLK_SEL_M0 0x00704 +#define XCVR_DIAG_PLLDRC_CTRL 0x10394 +#define XCVR_DIAG_HSCLK_SEL 0x10398 +#define XCVR_DIAG_HSCLK_DIV 0x1039c +#define TX_PSC_A0 0x10400 +#define TX_PSC_A1 0x10404 +#define TX_PSC_A2 0x10408 +#define TX_PSC_A3 0x1040c +#define RX_PSC_A0 0x20000 +#define RX_PSC_A1 0x20004 +#define RX_PSC_A2 0x20008 +#define RX_PSC_A3 0x2000c +#define PHY_PLL_CFG 0x30038 + +struct cdns_dp_phy { + void __iomem *base; /* DPTX registers base */ + void __iomem *sd_base; /* SD0801 registers base */ + u32 num_lanes; /* Number of lanes to use */ + u32 max_bit_rate; /* Maximum link bit rate to use (in Mbps) */ + struct device *dev; +}; + +static int cdns_dp_phy_init(struct phy *phy); +static void cdns_dp_phy_run(struct cdns_dp_phy *cdns_phy); +static void cdns_dp_phy_wait_pma_cmn_ready(struct cdns_dp_phy *cdns_phy); +static void cdns_dp_phy_pma_cfg(struct cdns_dp_phy *cdns_phy); +static void cdns_dp_phy_pma_cmn_cfg_25mhz(struct cdns_dp_phy *cdns_phy); +static void cdns_dp_phy_pma_lane_cfg(struct cdns_dp_phy *cdns_phy, + unsigned int lane); +static void cdns_dp_phy_pma_cmn_vco_cfg_25mhz(struct cdns_dp_phy *cdns_phy); +static void cdns_dp_phy_pma_cmn_rate(struct cdns_dp_phy *cdns_phy); +static void cdns_dp_phy_write_field(struct cdns_dp_phy *cdns_phy, + unsigned int offset, + unsigned char start_bit, + unsigned char num_bits, + unsigned int val); + +static const struct phy_ops cdns_dp_phy_ops = { + .init = cdns_dp_phy_init, + .owner = THIS_MODULE, +}; + +static int cdns_dp_phy_init(struct phy *phy) +{ + unsigned char lane_bits; + + struct cdns_dp_phy *cdns_phy = phy_get_drvdata(phy); + + writel(0x0003, cdns_phy->base + PHY_AUX_CTRL); /* enable AUX */ + + /* PHY PMA registers configuration function */ + cdns_dp_phy_pma_cfg(cdns_phy); + + /* + * Set lines power state to A0 + * Set lines pll clk enable to 0 + */ + + cdns_dp_phy_write_field(cdns_phy, PHY_PMA_XCVR_POWER_STATE_REQ, + PHY_POWER_STATE_LN_0, 6, 0x0000); + + if (cdns_phy->num_lanes >= 2) { + cdns_dp_phy_write_field(cdns_phy, + PHY_PMA_XCVR_POWER_STATE_REQ, + PHY_POWER_STATE_LN_1, 6, 0x0000); + + if (cdns_phy->num_lanes == 4) { + cdns_dp_phy_write_field(cdns_phy, + PHY_PMA_XCVR_POWER_STATE_REQ, + PHY_POWER_STATE_LN_2, 6, 0); + cdns_dp_phy_write_field(cdns_phy, + PHY_PMA_XCVR_POWER_STATE_REQ, + PHY_POWER_STATE_LN_3, 6, 0); + } + } + + cdns_dp_phy_write_field(cdns_phy, PHY_PMA_XCVR_PLLCLK_EN, + 0, 1, 0x0000); + + if (cdns_phy->num_lanes >= 2) { + cdns_dp_phy_write_field(cdns_phy, PHY_PMA_XCVR_PLLCLK_EN, + 1, 1, 0x0000); + if (cdns_phy->num_lanes == 4) { + cdns_dp_phy_write_field(cdns_phy, + PHY_PMA_XCVR_PLLCLK_EN, + 2, 1, 0x0000); + cdns_dp_phy_write_field(cdns_phy, + PHY_PMA_XCVR_PLLCLK_EN, + 3, 1, 0x0000); + } + } + + /* + * release phy_l0*_reset_n and pma_tx_elec_idle_ln_* based on + * used lanes + */ + lane_bits = (1 << cdns_phy->num_lanes) - 1; + writel(((0xF & ~lane_bits) << 4) | (0xF & lane_bits), + cdns_phy->base + PHY_RESET); + + /* release pma_xcvr_pllclk_en_ln_*, only for the master lane */ + writel(0x0001, cdns_phy->base + PHY_PMA_XCVR_PLLCLK_EN); + + /* PHY PMA registers configuration functions */ + cdns_dp_phy_pma_cmn_vco_cfg_25mhz(cdns_phy); + cdns_dp_phy_pma_cmn_rate(cdns_phy); + + /* take out of reset */ + cdns_dp_phy_write_field(cdns_phy, PHY_RESET, 8, 1, 1); + cdns_dp_phy_wait_pma_cmn_ready(cdns_phy); + cdns_dp_phy_run(cdns_phy); + + return 0; +} + +static void cdns_dp_phy_wait_pma_cmn_ready(struct cdns_dp_phy *cdns_phy) +{ + unsigned int reg; + int ret; + + ret = readl_poll_timeout(cdns_phy->base + PHY_PMA_CMN_READY, reg, + reg & 1, 0, 500); + if (ret == -ETIMEDOUT) + dev_err(cdns_phy->dev, + "timeout waiting for PMA common ready\n"); +} + +static void cdns_dp_phy_pma_cfg(struct cdns_dp_phy *cdns_phy) +{ + unsigned int i; + + /* PMA common configuration */ + cdns_dp_phy_pma_cmn_cfg_25mhz(cdns_phy); + + /* PMA lane configuration to deal with multi-link operation */ + for (i = 0; i < cdns_phy->num_lanes; i++) + cdns_dp_phy_pma_lane_cfg(cdns_phy, i); +} + +static void cdns_dp_phy_pma_cmn_cfg_25mhz(struct cdns_dp_phy *cdns_phy) +{ + /* refclock registers - assumes 25 MHz refclock */ + writel(0x0019, cdns_phy->sd_base + CMN_SSM_BIAS_TMR); + writel(0x0032, cdns_phy->sd_base + CMN_PLLSM0_PLLPRE_TMR); + writel(0x00D1, cdns_phy->sd_base + CMN_PLLSM0_PLLLOCK_TMR); + writel(0x0032, cdns_phy->sd_base + CMN_PLLSM1_PLLPRE_TMR); + writel(0x00D1, cdns_phy->sd_base + CMN_PLLSM1_PLLLOCK_TMR); + writel(0x007D, cdns_phy->sd_base + CMN_BGCAL_INIT_TMR); + writel(0x007D, cdns_phy->sd_base + CMN_BGCAL_ITER_TMR); + writel(0x0019, cdns_phy->sd_base + CMN_IBCAL_INIT_TMR); + writel(0x001E, cdns_phy->sd_base + CMN_TXPUCAL_INIT_TMR); + writel(0x0006, cdns_phy->sd_base + CMN_TXPUCAL_ITER_TMR); + writel(0x001E, cdns_phy->sd_base + CMN_TXPDCAL_INIT_TMR); + writel(0x0006, cdns_phy->sd_base + CMN_TXPDCAL_ITER_TMR); + writel(0x02EE, cdns_phy->sd_base + CMN_RXCAL_INIT_TMR); + writel(0x0006, cdns_phy->sd_base + CMN_RXCAL_ITER_TMR); + writel(0x0002, cdns_phy->sd_base + CMN_SD_CAL_INIT_TMR); + writel(0x0002, cdns_phy->sd_base + CMN_SD_CAL_ITER_TMR); + writel(0x000E, cdns_phy->sd_base + CMN_SD_CAL_REFTIM_START); + writel(0x012B, cdns_phy->sd_base + CMN_SD_CAL_PLLCNT_START); + /* PLL registers */ + writel(0x0409, cdns_phy->sd_base + CMN_PDIAG_PLL0_CP_PADJ_M0); + writel(0x1001, cdns_phy->sd_base + CMN_PDIAG_PLL0_CP_IADJ_M0); + writel(0x0F08, cdns_phy->sd_base + CMN_PDIAG_PLL0_FILT_PADJ_M0); + writel(0x0004, cdns_phy->sd_base + CMN_PLL0_DSM_DIAG_M0); + writel(0x00FA, cdns_phy->sd_base + CMN_PLL0_VCOCAL_INIT_TMR); + writel(0x0004, cdns_phy->sd_base + CMN_PLL0_VCOCAL_ITER_TMR); + writel(0x00FA, cdns_phy->sd_base + CMN_PLL1_VCOCAL_INIT_TMR); + writel(0x0004, cdns_phy->sd_base + CMN_PLL1_VCOCAL_ITER_TMR); + writel(0x0318, cdns_phy->sd_base + CMN_PLL0_VCOCAL_REFTIM_START); +} + +static void cdns_dp_phy_pma_cmn_vco_cfg_25mhz(struct cdns_dp_phy *cdns_phy) +{ + /* Assumes 25 MHz refclock */ + switch (cdns_phy->max_bit_rate) { + /* Setting VCO for 10.8GHz */ + case 2700: + case 5400: + writel(0x01B0, cdns_phy->sd_base + CMN_PLL0_INTDIV_M0); + writel(0x0000, cdns_phy->sd_base + CMN_PLL0_FRACDIVL_M0); + writel(0x0002, cdns_phy->sd_base + CMN_PLL0_FRACDIVH_M0); + writel(0x0120, cdns_phy->sd_base + CMN_PLL0_HIGH_THR_M0); + break; + /* Setting VCO for 9.72GHz */ + case 2430: + case 3240: + writel(0x0184, cdns_phy->sd_base + CMN_PLL0_INTDIV_M0); + writel(0xCCCD, cdns_phy->sd_base + CMN_PLL0_FRACDIVL_M0); + writel(0x0002, cdns_phy->sd_base + CMN_PLL0_FRACDIVH_M0); + writel(0x0104, cdns_phy->sd_base + CMN_PLL0_HIGH_THR_M0); + break; + /* Setting VCO for 8.64GHz */ + case 2160: + case 4320: + writel(0x0159, cdns_phy->sd_base + CMN_PLL0_INTDIV_M0); + writel(0x999A, cdns_phy->sd_base + CMN_PLL0_FRACDIVL_M0); + writel(0x0002, cdns_phy->sd_base + CMN_PLL0_FRACDIVH_M0); + writel(0x00E7, cdns_phy->sd_base + CMN_PLL0_HIGH_THR_M0); + break; + /* Setting VCO for 8.1GHz */ + case 8100: + writel(0x0144, cdns_phy->sd_base + CMN_PLL0_INTDIV_M0); + writel(0x0000, cdns_phy->sd_base + CMN_PLL0_FRACDIVL_M0); + writel(0x0002, cdns_phy->sd_base + CMN_PLL0_FRACDIVH_M0); + writel(0x00D8, cdns_phy->sd_base + CMN_PLL0_HIGH_THR_M0); + break; + } + + writel(0x0002, cdns_phy->sd_base + CMN_PDIAG_PLL0_CTRL_M0); + writel(0x0318, cdns_phy->sd_base + CMN_PLL0_VCOCAL_PLLCNT_START); +} + +static void cdns_dp_phy_pma_cmn_rate(struct cdns_dp_phy *cdns_phy) +{ + unsigned int clk_sel_val = 0; + unsigned int hsclk_div_val = 0; + unsigned int i; + + /* 16'h0000 for single DP link configuration */ + writel(0x0000, cdns_phy->sd_base + PHY_PLL_CFG); + + switch (cdns_phy->max_bit_rate) { + case 1620: + clk_sel_val = 0x0f01; + hsclk_div_val = 2; + break; + case 2160: + case 2430: + case 2700: + clk_sel_val = 0x0701; + hsclk_div_val = 1; + break; + case 3240: + clk_sel_val = 0x0b00; + hsclk_div_val = 2; + break; + case 4320: + case 5400: + clk_sel_val = 0x0301; + hsclk_div_val = 0; + break; + case 8100: + clk_sel_val = 0x0200; + hsclk_div_val = 0; + break; + } + + writel(clk_sel_val, cdns_phy->sd_base + CMN_PDIAG_PLL0_CLK_SEL_M0); + + /* PMA lane configuration to deal with multi-link operation */ + for (i = 0; i < cdns_phy->num_lanes; i++) { + writel(hsclk_div_val, + cdns_phy->sd_base + (XCVR_DIAG_HSCLK_DIV | (i<<11))); + } +} + +static void cdns_dp_phy_pma_lane_cfg(struct cdns_dp_phy *cdns_phy, + unsigned int lane) +{ + unsigned int lane_bits = (lane & LANE_MASK) << 11; + + /* Writing Tx/Rx Power State Controllers registers */ + writel(0x00FB, cdns_phy->sd_base + (TX_PSC_A0 | lane_bits)); + writel(0x04AA, cdns_phy->sd_base + (TX_PSC_A2 | lane_bits)); + writel(0x04AA, cdns_phy->sd_base + (TX_PSC_A3 | lane_bits)); + writel(0x0000, cdns_phy->sd_base + (RX_PSC_A0 | lane_bits)); + writel(0x0000, cdns_phy->sd_base + (RX_PSC_A2 | lane_bits)); + writel(0x0000, cdns_phy->sd_base + (RX_PSC_A3 | lane_bits)); + + writel(0x0001, cdns_phy->sd_base + (XCVR_DIAG_PLLDRC_CTRL | lane_bits)); + writel(0x0000, cdns_phy->sd_base + (XCVR_DIAG_HSCLK_SEL | lane_bits)); +} + +static void cdns_dp_phy_run(struct cdns_dp_phy *cdns_phy) +{ + unsigned int read_val; + u32 write_val1 = 0; + u32 write_val2 = 0; + u32 mask = 0; + int ret; + + /* + * waiting for ACK of pma_xcvr_pllclk_en_ln_*, only for the + * master lane + */ + ret = readl_poll_timeout(cdns_phy->base + PHY_PMA_XCVR_PLLCLK_EN_ACK, + read_val, read_val & 1, 0, POLL_TIMEOUT_US); + if (ret == -ETIMEDOUT) + dev_err(cdns_phy->dev, + "timeout waiting for link PLL clock enable ack\n"); + + ndelay(100); + + switch (cdns_phy->num_lanes) { + + case 1: /* lane 0 */ + write_val1 = 0x00000004; + write_val2 = 0x00000001; + mask = 0x0000003f; + break; + case 2: /* lane 0-1 */ + write_val1 = 0x00000404; + write_val2 = 0x00000101; + mask = 0x00003f3f; + break; + case 4: /* lane 0-3 */ + write_val1 = 0x04040404; + write_val2 = 0x01010101; + mask = 0x3f3f3f3f; + break; + } + + writel(write_val1, cdns_phy->base + PHY_PMA_XCVR_POWER_STATE_REQ); + + ret = readl_poll_timeout(cdns_phy->base + PHY_PMA_XCVR_POWER_STATE_ACK, + read_val, (read_val & mask) == write_val1, 0, + POLL_TIMEOUT_US); + if (ret == -ETIMEDOUT) + dev_err(cdns_phy->dev, + "timeout waiting for link power state ack\n"); + + writel(0, cdns_phy->base + PHY_PMA_XCVR_POWER_STATE_REQ); + ndelay(100); + + writel(write_val2, cdns_phy->base + PHY_PMA_XCVR_POWER_STATE_REQ); + + ret = readl_poll_timeout(cdns_phy->base + PHY_PMA_XCVR_POWER_STATE_ACK, + read_val, (read_val & mask) == write_val2, 0, + POLL_TIMEOUT_US); + if (ret == -ETIMEDOUT) + dev_err(cdns_phy->dev, + "timeout waiting for link power state ack\n"); + + writel(0, cdns_phy->base + PHY_PMA_XCVR_POWER_STATE_REQ); + ndelay(100); +} + +static void cdns_dp_phy_write_field(struct cdns_dp_phy *cdns_phy, + unsigned int offset, + unsigned char start_bit, + unsigned char num_bits, + unsigned int val) +{ + unsigned int read_val; + + read_val = readl(cdns_phy->base + offset); + writel(((val << start_bit) | (read_val & ~(((1 << num_bits) - 1) << + start_bit))), cdns_phy->base + offset); +} + +static int cdns_dp_phy_probe(struct platform_device *pdev) +{ + struct resource *regs; + struct cdns_dp_phy *cdns_phy; + struct device *dev = &pdev->dev; + struct phy_provider *phy_provider; + struct phy *phy; + int err; + + cdns_phy = devm_kzalloc(dev, sizeof(*cdns_phy), GFP_KERNEL); + if (!cdns_phy) + return -ENOMEM; + + cdns_phy->dev = &pdev->dev; + + phy = devm_phy_create(dev, NULL, &cdns_dp_phy_ops); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create DisplayPort PHY\n"); + return PTR_ERR(phy); + } + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + cdns_phy->base = devm_ioremap_resource(&pdev->dev, regs); + if (IS_ERR(cdns_phy->base)) + return PTR_ERR(cdns_phy->base); + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 1); + cdns_phy->sd_base = devm_ioremap_resource(&pdev->dev, regs); + if (IS_ERR(cdns_phy->sd_base)) + return PTR_ERR(cdns_phy->sd_base); + + err = device_property_read_u32(dev, "num_lanes", + &(cdns_phy->num_lanes)); + if (err) + cdns_phy->num_lanes = DEFAULT_NUM_LANES; + + switch (cdns_phy->num_lanes) { + case 1: + case 2: + case 4: + /* valid number of lanes */ + break; + default: + dev_err(dev, "unsupported number of lanes: %d\n", + cdns_phy->num_lanes); + return -EINVAL; + } + + err = device_property_read_u32(dev, "max_bit_rate", + &(cdns_phy->max_bit_rate)); + if (err) + cdns_phy->max_bit_rate = DEFAULT_MAX_BIT_RATE; + + switch (cdns_phy->max_bit_rate) { + case 2160: + case 2430: + case 2700: + case 3240: + case 4320: + case 5400: + case 8100: + /* valid bit rate */ + break; + default: + dev_err(dev, "unsupported max bit rate: %dMbps\n", + cdns_phy->max_bit_rate); + return -EINVAL; + } + + phy_set_drvdata(phy, cdns_phy); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + dev_info(dev, "%d lanes, max bit rate %d.%03d Gbps\n", + cdns_phy->num_lanes, + cdns_phy->max_bit_rate / 1000, + cdns_phy->max_bit_rate % 1000); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id cdns_dp_phy_of_match[] = { + { + .compatible = "cdns,dp-phy" + }, + {} +}; +MODULE_DEVICE_TABLE(of, cdns_dp_phy_of_match); + +static struct platform_driver cdns_dp_phy_driver = { + .probe = cdns_dp_phy_probe, + .driver = { + .name = "cdns-dp-phy", + .of_match_table = cdns_dp_phy_of_match, + } +}; +module_platform_driver(cdns_dp_phy_driver); + +MODULE_AUTHOR("Cadence Design Systems, Inc."); +MODULE_DESCRIPTION("Cadence MHDP PHY driver"); +MODULE_LICENSE("GPL v2"); From 22fa10e52ab30cf33c200d9f73be22600427b739 Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Mon, 14 May 2018 15:42:21 -0700 Subject: [PATCH 04/41] phy: qcom-qmp: Quiet -EPROBE_DEFER from qcom_qmp_phy_probe() The -EPROBE_DEFER virus demands special case code to avoid printing error messages when the error is only -EPROBE_DEFER. Spread the virus to a new host: qcom_qmp_phy_probe(). Specifically handle when our regulators might not be ready yet. Signed-off-by: Douglas Anderson Reviewed-by: Vivek Gautam Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/qualcomm/phy-qcom-qmp.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.c b/drivers/phy/qualcomm/phy-qcom-qmp.c index 4c470104a0d61..72efc2e14ab6e 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp.c @@ -1586,7 +1586,9 @@ static int qcom_qmp_phy_probe(struct platform_device *pdev) ret = qcom_qmp_phy_vreg_init(dev); if (ret) { - dev_err(dev, "failed to get regulator supplies\n"); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get regulator supplies: %d\n", + ret); return ret; } From 6100ac72dc0ba002a6ff602e8a043a9800f2ee0b Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Mon, 14 May 2018 15:42:22 -0700 Subject: [PATCH 05/41] phy: qcom-qusb2: Quiet -EPROBE_DEFER from qusb2_phy_probe() The -EPROBE_DEFER virus demands special case code to avoid printing error messages when the error is only -EPROBE_DEFER. Spread the virus to a new host: qusb2_phy_probe(). Specifically handle when our regulators might not be ready yet. Signed-off-by: Douglas Anderson Reviewed-by: Vivek Gautam Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/qualcomm/phy-qcom-qusb2.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/phy/qualcomm/phy-qcom-qusb2.c b/drivers/phy/qualcomm/phy-qcom-qusb2.c index e70e425f26f50..9ce531194f8af 100644 --- a/drivers/phy/qualcomm/phy-qcom-qusb2.c +++ b/drivers/phy/qualcomm/phy-qcom-qusb2.c @@ -800,7 +800,9 @@ static int qusb2_phy_probe(struct platform_device *pdev) ret = devm_regulator_bulk_get(dev, num, qphy->vregs); if (ret) { - dev_err(dev, "failed to get regulator supplies\n"); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get regulator supplies: %d\n", + ret); return ret; } From 270d5aad53cdf93c00271eaa4568578a1ed86448 Mon Sep 17 00:00:00 2001 From: Kunihiko Hayashi Date: Wed, 22 Aug 2018 12:50:31 +0900 Subject: [PATCH 06/41] dt-bindings: phy: add DT bindings for UniPhier USB3 PHY driver Add DT bindings for PHY interface built into USB3 controller implemented in UniPhier SoCs. Signed-off-by: Kunihiko Hayashi Signed-off-by: Kishon Vijay Abraham I --- .../bindings/phy/uniphier-usb3-hsphy.txt | 69 +++++++++++++++++++ .../bindings/phy/uniphier-usb3-ssphy.txt | 57 +++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/uniphier-usb3-hsphy.txt create mode 100644 Documentation/devicetree/bindings/phy/uniphier-usb3-ssphy.txt diff --git a/Documentation/devicetree/bindings/phy/uniphier-usb3-hsphy.txt b/Documentation/devicetree/bindings/phy/uniphier-usb3-hsphy.txt new file mode 100644 index 0000000000000..e8d8086a7ae95 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/uniphier-usb3-hsphy.txt @@ -0,0 +1,69 @@ +Socionext UniPhier USB3 High-Speed (HS) PHY + +This describes the devicetree bindings for PHY interfaces built into +USB3 controller implemented on Socionext UniPhier SoCs. +Although the controller includes High-Speed PHY and Super-Speed PHY, +this describes about High-Speed PHY. + +Required properties: +- compatible: Should contain one of the following: + "socionext,uniphier-pro4-usb3-hsphy" - for Pro4 SoC + "socionext,uniphier-pxs2-usb3-hsphy" - for PXs2 SoC + "socionext,uniphier-ld20-usb3-hsphy" - for LD20 SoC + "socionext,uniphier-pxs3-usb3-hsphy" - for PXs3 SoC +- reg: Specifies offset and length of the register set for the device. +- #phy-cells: Should be 0. +- clocks: A list of phandles to the clock gate for USB3 glue layer. + According to the clock-names, appropriate clocks are required. +- clock-names: Should contain the following: + "gio", "link" - for Pro4 SoC + "phy", "phy-ext", "link" - for PXs3 SoC, "phy-ext" is optional. + "phy", "link" - for others +- resets: A list of phandles to the reset control for USB3 glue layer. + According to the reset-names, appropriate resets are required. +- reset-names: Should contain the following: + "gio", "link" - for Pro4 SoC + "phy", "link" - for others + +Optional properties: +- vbus-supply: A phandle to the regulator for USB VBUS. +- nvmem-cells: Phandles to nvmem cell that contains the trimming data. + Available only for HS-PHY implemented on LD20 and PXs3, and + if unspecified, default value is used. +- nvmem-cell-names: Should be the following names, which correspond to + each nvmem-cells. + All of the 3 parameters associated with the following names are + required for each port, if any one is omitted, the trimming data + of the port will not be set at all. + "rterm", "sel_t", "hs_i" - Each cell name for phy parameters + +Refer to phy/phy-bindings.txt for the generic PHY binding properties. + +Example: + + usb-glue@65b00000 { + compatible = "socionext,uniphier-ld20-dwc3-glue", + "simple-mfd"; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0 0x65b00000 0x400>; + + usb_vbus0: regulator { + ... + }; + + usb_hsphy0: hs-phy@200 { + compatible = "socionext,uniphier-ld20-usb3-hsphy"; + reg = <0x200 0x10>; + #phy-cells = <0>; + clock-names = "link", "phy"; + clocks = <&sys_clk 14>, <&sys_clk 16>; + reset-names = "link", "phy"; + resets = <&sys_rst 14>, <&sys_rst 16>; + vbus-supply = <&usb_vbus0>; + nvmem-cell-names = "rterm", "sel_t", "hs_i"; + nvmem-cells = <&usb_rterm0>, <&usb_sel_t0>, + <&usb_hs_i0>; + }; + ... + }; diff --git a/Documentation/devicetree/bindings/phy/uniphier-usb3-ssphy.txt b/Documentation/devicetree/bindings/phy/uniphier-usb3-ssphy.txt new file mode 100644 index 0000000000000..490b815445e81 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/uniphier-usb3-ssphy.txt @@ -0,0 +1,57 @@ +Socionext UniPhier USB3 Super-Speed (SS) PHY + +This describes the devicetree bindings for PHY interfaces built into +USB3 controller implemented on Socionext UniPhier SoCs. +Although the controller includes High-Speed PHY and Super-Speed PHY, +this describes about Super-Speed PHY. + +Required properties: +- compatible: Should contain one of the following: + "socionext,uniphier-pro4-usb3-ssphy" - for Pro4 SoC + "socionext,uniphier-pxs2-usb3-ssphy" - for PXs2 SoC + "socionext,uniphier-ld20-usb3-ssphy" - for LD20 SoC + "socionext,uniphier-pxs3-usb3-ssphy" - for PXs3 SoC +- reg: Specifies offset and length of the register set for the device. +- #phy-cells: Should be 0. +- clocks: A list of phandles to the clock gate for USB3 glue layer. + According to the clock-names, appropriate clocks are required. +- clock-names: + "gio", "link" - for Pro4 SoC + "phy", "phy-ext", "link" - for PXs3 SoC, "phy-ext" is optional. + "phy", "link" - for others +- resets: A list of phandles to the reset control for USB3 glue layer. + According to the reset-names, appropriate resets are required. +- reset-names: + "gio", "link" - for Pro4 SoC + "phy", "link" - for others + +Optional properties: +- vbus-supply: A phandle to the regulator for USB VBUS. + +Refer to phy/phy-bindings.txt for the generic PHY binding properties. + +Example: + + usb-glue@65b00000 { + compatible = "socionext,uniphier-ld20-dwc3-glue", + "simple-mfd"; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0 0x65b00000 0x400>; + + usb_vbus0: regulator { + ... + }; + + usb_ssphy0: ss-phy@300 { + compatible = "socionext,uniphier-ld20-usb3-ssphy"; + reg = <0x300 0x10>; + #phy-cells = <0>; + clock-names = "link", "phy"; + clocks = <&sys_clk 14>, <&sys_clk 16>; + reset-names = "link", "phy"; + resets = <&sys_rst 14>, <&sys_rst 16>; + vbus-supply = <&usb_vbus0>; + }; + ... + }; From 5ab43d0f86979d6741c1dda685af3e053982e03e Mon Sep 17 00:00:00 2001 From: Kunihiko Hayashi Date: Wed, 22 Aug 2018 12:50:32 +0900 Subject: [PATCH 07/41] phy: socionext: add USB3 PHY driver for UniPhier SoC Add a driver for PHY interface built into USB3 controller implemented in UniPhier SoCs. This driver supports High-Speed PHY and Super-Speed PHY. Signed-off-by: Kunihiko Hayashi Signed-off-by: Motoya Tanigawa Signed-off-by: Masami Hiramatsu Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/Kconfig | 1 + drivers/phy/Makefile | 1 + drivers/phy/socionext/Kconfig | 12 + drivers/phy/socionext/Makefile | 6 + drivers/phy/socionext/phy-uniphier-usb3hs.c | 422 ++++++++++++++++++++ drivers/phy/socionext/phy-uniphier-usb3ss.c | 349 ++++++++++++++++ 6 files changed, 791 insertions(+) create mode 100644 drivers/phy/socionext/Kconfig create mode 100644 drivers/phy/socionext/Makefile create mode 100644 drivers/phy/socionext/phy-uniphier-usb3hs.c create mode 100644 drivers/phy/socionext/phy-uniphier-usb3ss.c diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index cc47f85088501..a99d492b299b6 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -54,6 +54,7 @@ source "drivers/phy/ralink/Kconfig" source "drivers/phy/renesas/Kconfig" source "drivers/phy/rockchip/Kconfig" source "drivers/phy/samsung/Kconfig" +source "drivers/phy/socionext/Kconfig" source "drivers/phy/st/Kconfig" source "drivers/phy/tegra/Kconfig" source "drivers/phy/ti/Kconfig" diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index ba48acdd9ed16..d1500af3fe0e3 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -22,5 +22,6 @@ obj-y += broadcom/ \ qualcomm/ \ ralink/ \ samsung/ \ + socionext/ \ st/ \ ti/ diff --git a/drivers/phy/socionext/Kconfig b/drivers/phy/socionext/Kconfig new file mode 100644 index 0000000000000..4a172fce92512 --- /dev/null +++ b/drivers/phy/socionext/Kconfig @@ -0,0 +1,12 @@ +# +# PHY drivers for Socionext platforms. +# + +config PHY_UNIPHIER_USB3 + tristate "UniPhier USB3 PHY driver" + depends on ARCH_UNIPHIER || COMPILE_TEST + depends on OF && HAS_IOMEM + select GENERIC_PHY + help + Enable this to support USB PHY implemented in USB3 controller + on UniPhier SoCs. This controller supports USB3.0 and lower speed. diff --git a/drivers/phy/socionext/Makefile b/drivers/phy/socionext/Makefile new file mode 100644 index 0000000000000..e230fa314bbad --- /dev/null +++ b/drivers/phy/socionext/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the phy drivers. +# + +obj-$(CONFIG_PHY_UNIPHIER_USB3) += phy-uniphier-usb3hs.o phy-uniphier-usb3ss.o diff --git a/drivers/phy/socionext/phy-uniphier-usb3hs.c b/drivers/phy/socionext/phy-uniphier-usb3hs.c new file mode 100644 index 0000000000000..b1b048be61664 --- /dev/null +++ b/drivers/phy/socionext/phy-uniphier-usb3hs.c @@ -0,0 +1,422 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * phy-uniphier-usb3hs.c - HS-PHY driver for Socionext UniPhier USB3 controller + * Copyright 2015-2018 Socionext Inc. + * Author: + * Kunihiko Hayashi + * Contributors: + * Motoya Tanigawa + * Masami Hiramatsu + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HSPHY_CFG0 0x0 +#define HSPHY_CFG0_HS_I_MASK GENMASK(31, 28) +#define HSPHY_CFG0_HSDISC_MASK GENMASK(27, 26) +#define HSPHY_CFG0_SWING_MASK GENMASK(17, 16) +#define HSPHY_CFG0_SEL_T_MASK GENMASK(15, 12) +#define HSPHY_CFG0_RTERM_MASK GENMASK(7, 6) +#define HSPHY_CFG0_TRIMMASK (HSPHY_CFG0_HS_I_MASK \ + | HSPHY_CFG0_SEL_T_MASK \ + | HSPHY_CFG0_RTERM_MASK) + +#define HSPHY_CFG1 0x4 +#define HSPHY_CFG1_DAT_EN BIT(29) +#define HSPHY_CFG1_ADR_EN BIT(28) +#define HSPHY_CFG1_ADR_MASK GENMASK(27, 16) +#define HSPHY_CFG1_DAT_MASK GENMASK(23, 16) + +#define PHY_F(regno, msb, lsb) { (regno), (msb), (lsb) } + +#define LS_SLEW PHY_F(10, 6, 6) /* LS mode slew rate */ +#define FS_LS_DRV PHY_F(10, 5, 5) /* FS/LS slew rate */ + +#define MAX_PHY_PARAMS 2 + +struct uniphier_u3hsphy_param { + struct { + int reg_no; + int msb; + int lsb; + } field; + u8 value; +}; + +struct uniphier_u3hsphy_trim_param { + unsigned int rterm; + unsigned int sel_t; + unsigned int hs_i; +}; + +#define trim_param_is_valid(p) ((p)->rterm || (p)->sel_t || (p)->hs_i) + +struct uniphier_u3hsphy_priv { + struct device *dev; + void __iomem *base; + struct clk *clk, *clk_parent, *clk_ext; + struct reset_control *rst, *rst_parent; + struct regulator *vbus; + const struct uniphier_u3hsphy_soc_data *data; +}; + +struct uniphier_u3hsphy_soc_data { + int nparams; + const struct uniphier_u3hsphy_param param[MAX_PHY_PARAMS]; + u32 config0; + u32 config1; + void (*trim_func)(struct uniphier_u3hsphy_priv *priv, u32 *pconfig, + struct uniphier_u3hsphy_trim_param *pt); +}; + +static void uniphier_u3hsphy_trim_ld20(struct uniphier_u3hsphy_priv *priv, + u32 *pconfig, + struct uniphier_u3hsphy_trim_param *pt) +{ + *pconfig &= ~HSPHY_CFG0_RTERM_MASK; + *pconfig |= FIELD_PREP(HSPHY_CFG0_RTERM_MASK, pt->rterm); + + *pconfig &= ~HSPHY_CFG0_SEL_T_MASK; + *pconfig |= FIELD_PREP(HSPHY_CFG0_SEL_T_MASK, pt->sel_t); + + *pconfig &= ~HSPHY_CFG0_HS_I_MASK; + *pconfig |= FIELD_PREP(HSPHY_CFG0_HS_I_MASK, pt->hs_i); +} + +static int uniphier_u3hsphy_get_nvparam(struct uniphier_u3hsphy_priv *priv, + const char *name, unsigned int *val) +{ + struct nvmem_cell *cell; + u8 *buf; + + cell = devm_nvmem_cell_get(priv->dev, name); + if (IS_ERR(cell)) + return PTR_ERR(cell); + + buf = nvmem_cell_read(cell, NULL); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + *val = *buf; + + kfree(buf); + + return 0; +} + +static int uniphier_u3hsphy_get_nvparams(struct uniphier_u3hsphy_priv *priv, + struct uniphier_u3hsphy_trim_param *pt) +{ + int ret; + + ret = uniphier_u3hsphy_get_nvparam(priv, "rterm", &pt->rterm); + if (ret) + return ret; + + ret = uniphier_u3hsphy_get_nvparam(priv, "sel_t", &pt->sel_t); + if (ret) + return ret; + + ret = uniphier_u3hsphy_get_nvparam(priv, "hs_i", &pt->hs_i); + if (ret) + return ret; + + return 0; +} + +static int uniphier_u3hsphy_update_config(struct uniphier_u3hsphy_priv *priv, + u32 *pconfig) +{ + struct uniphier_u3hsphy_trim_param trim; + int ret, trimmed = 0; + + if (priv->data->trim_func) { + ret = uniphier_u3hsphy_get_nvparams(priv, &trim); + if (ret == -EPROBE_DEFER) + return ret; + + /* + * call trim_func only when trimming parameters that aren't + * all-zero can be acquired. All-zero parameters mean nothing + * has been written to nvmem. + */ + if (!ret && trim_param_is_valid(&trim)) { + priv->data->trim_func(priv, pconfig, &trim); + trimmed = 1; + } else { + dev_dbg(priv->dev, "can't get parameter from nvmem\n"); + } + } + + /* use default parameters without trimming values */ + if (!trimmed) { + *pconfig &= ~HSPHY_CFG0_HSDISC_MASK; + *pconfig |= FIELD_PREP(HSPHY_CFG0_HSDISC_MASK, 3); + } + + return 0; +} + +static void uniphier_u3hsphy_set_param(struct uniphier_u3hsphy_priv *priv, + const struct uniphier_u3hsphy_param *p) +{ + u32 val; + u32 field_mask = GENMASK(p->field.msb, p->field.lsb); + u8 data; + + val = readl(priv->base + HSPHY_CFG1); + val &= ~HSPHY_CFG1_ADR_MASK; + val |= FIELD_PREP(HSPHY_CFG1_ADR_MASK, p->field.reg_no) + | HSPHY_CFG1_ADR_EN; + writel(val, priv->base + HSPHY_CFG1); + + val = readl(priv->base + HSPHY_CFG1); + val &= ~HSPHY_CFG1_ADR_EN; + writel(val, priv->base + HSPHY_CFG1); + + val = readl(priv->base + HSPHY_CFG1); + val &= ~FIELD_PREP(HSPHY_CFG1_DAT_MASK, field_mask); + data = field_mask & (p->value << p->field.lsb); + val |= FIELD_PREP(HSPHY_CFG1_DAT_MASK, data) | HSPHY_CFG1_DAT_EN; + writel(val, priv->base + HSPHY_CFG1); + + val = readl(priv->base + HSPHY_CFG1); + val &= ~HSPHY_CFG1_DAT_EN; + writel(val, priv->base + HSPHY_CFG1); +} + +static int uniphier_u3hsphy_power_on(struct phy *phy) +{ + struct uniphier_u3hsphy_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = clk_prepare_enable(priv->clk_ext); + if (ret) + return ret; + + ret = clk_prepare_enable(priv->clk); + if (ret) + goto out_clk_ext_disable; + + ret = reset_control_deassert(priv->rst); + if (ret) + goto out_clk_disable; + + if (priv->vbus) { + ret = regulator_enable(priv->vbus); + if (ret) + goto out_rst_assert; + } + + return 0; + +out_rst_assert: + reset_control_assert(priv->rst); +out_clk_disable: + clk_disable_unprepare(priv->clk); +out_clk_ext_disable: + clk_disable_unprepare(priv->clk_ext); + + return ret; +} + +static int uniphier_u3hsphy_power_off(struct phy *phy) +{ + struct uniphier_u3hsphy_priv *priv = phy_get_drvdata(phy); + + if (priv->vbus) + regulator_disable(priv->vbus); + + reset_control_assert(priv->rst); + clk_disable_unprepare(priv->clk); + clk_disable_unprepare(priv->clk_ext); + + return 0; +} + +static int uniphier_u3hsphy_init(struct phy *phy) +{ + struct uniphier_u3hsphy_priv *priv = phy_get_drvdata(phy); + u32 config0, config1; + int i, ret; + + ret = clk_prepare_enable(priv->clk_parent); + if (ret) + return ret; + + ret = reset_control_deassert(priv->rst_parent); + if (ret) + goto out_clk_disable; + + if (!priv->data->config0 && !priv->data->config1) + return 0; + + config0 = priv->data->config0; + config1 = priv->data->config1; + + ret = uniphier_u3hsphy_update_config(priv, &config0); + if (ret) + goto out_rst_assert; + + writel(config0, priv->base + HSPHY_CFG0); + writel(config1, priv->base + HSPHY_CFG1); + + for (i = 0; i < priv->data->nparams; i++) + uniphier_u3hsphy_set_param(priv, &priv->data->param[i]); + + return 0; + +out_rst_assert: + reset_control_assert(priv->rst_parent); +out_clk_disable: + clk_disable_unprepare(priv->clk_parent); + + return ret; +} + +static int uniphier_u3hsphy_exit(struct phy *phy) +{ + struct uniphier_u3hsphy_priv *priv = phy_get_drvdata(phy); + + reset_control_assert(priv->rst_parent); + clk_disable_unprepare(priv->clk_parent); + + return 0; +} + +static const struct phy_ops uniphier_u3hsphy_ops = { + .init = uniphier_u3hsphy_init, + .exit = uniphier_u3hsphy_exit, + .power_on = uniphier_u3hsphy_power_on, + .power_off = uniphier_u3hsphy_power_off, + .owner = THIS_MODULE, +}; + +static int uniphier_u3hsphy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct uniphier_u3hsphy_priv *priv; + struct phy_provider *phy_provider; + struct resource *res; + struct phy *phy; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->data = of_device_get_match_data(dev); + if (WARN_ON(!priv->data || + priv->data->nparams > MAX_PHY_PARAMS)) + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->clk = devm_clk_get(dev, "phy"); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + priv->clk_parent = devm_clk_get(dev, "link"); + if (IS_ERR(priv->clk_parent)) + return PTR_ERR(priv->clk_parent); + + priv->clk_ext = devm_clk_get(dev, "phy-ext"); + if (IS_ERR(priv->clk_ext)) { + if (PTR_ERR(priv->clk_ext) == -ENOENT) + priv->clk_ext = NULL; + else + return PTR_ERR(priv->clk_ext); + } + + priv->rst = devm_reset_control_get_shared(dev, "phy"); + if (IS_ERR(priv->rst)) + return PTR_ERR(priv->rst); + + priv->rst_parent = devm_reset_control_get_shared(dev, "link"); + if (IS_ERR(priv->rst_parent)) + return PTR_ERR(priv->rst_parent); + + priv->vbus = devm_regulator_get_optional(dev, "vbus"); + if (IS_ERR(priv->vbus)) { + if (PTR_ERR(priv->vbus) == -EPROBE_DEFER) + return PTR_ERR(priv->vbus); + priv->vbus = NULL; + } + + phy = devm_phy_create(dev, dev->of_node, &uniphier_u3hsphy_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + phy_set_drvdata(phy, priv); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct uniphier_u3hsphy_soc_data uniphier_pxs2_data = { + .nparams = 0, +}; + +static const struct uniphier_u3hsphy_soc_data uniphier_ld20_data = { + .nparams = 2, + .param = { + { LS_SLEW, 1 }, + { FS_LS_DRV, 1 }, + }, + .trim_func = uniphier_u3hsphy_trim_ld20, + .config0 = 0x92316680, + .config1 = 0x00000106, +}; + +static const struct uniphier_u3hsphy_soc_data uniphier_pxs3_data = { + .nparams = 0, + .trim_func = uniphier_u3hsphy_trim_ld20, + .config0 = 0x92316680, + .config1 = 0x00000106, +}; + +static const struct of_device_id uniphier_u3hsphy_match[] = { + { + .compatible = "socionext,uniphier-pxs2-usb3-hsphy", + .data = &uniphier_pxs2_data, + }, + { + .compatible = "socionext,uniphier-ld20-usb3-hsphy", + .data = &uniphier_ld20_data, + }, + { + .compatible = "socionext,uniphier-pxs3-usb3-hsphy", + .data = &uniphier_pxs3_data, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, uniphier_u3hsphy_match); + +static struct platform_driver uniphier_u3hsphy_driver = { + .probe = uniphier_u3hsphy_probe, + .driver = { + .name = "uniphier-usb3-hsphy", + .of_match_table = uniphier_u3hsphy_match, + }, +}; + +module_platform_driver(uniphier_u3hsphy_driver); + +MODULE_AUTHOR("Kunihiko Hayashi "); +MODULE_DESCRIPTION("UniPhier HS-PHY driver for USB3 controller"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/socionext/phy-uniphier-usb3ss.c b/drivers/phy/socionext/phy-uniphier-usb3ss.c new file mode 100644 index 0000000000000..4be95679c7d86 --- /dev/null +++ b/drivers/phy/socionext/phy-uniphier-usb3ss.c @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * phy-uniphier-usb3ss.c - SS-PHY driver for Socionext UniPhier USB3 controller + * Copyright 2015-2018 Socionext Inc. + * Author: + * Kunihiko Hayashi + * Contributors: + * Motoya Tanigawa + * Masami Hiramatsu + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SSPHY_TESTI 0x0 +#define SSPHY_TESTO 0x4 +#define TESTI_DAT_MASK GENMASK(13, 6) +#define TESTI_ADR_MASK GENMASK(5, 1) +#define TESTI_WR_EN BIT(0) + +#define PHY_F(regno, msb, lsb) { (regno), (msb), (lsb) } + +#define CDR_CPD_TRIM PHY_F(7, 3, 0) /* RxPLL charge pump current */ +#define CDR_CPF_TRIM PHY_F(8, 3, 0) /* RxPLL charge pump current 2 */ +#define TX_PLL_TRIM PHY_F(9, 3, 0) /* TxPLL charge pump current */ +#define BGAP_TRIM PHY_F(11, 3, 0) /* Bandgap voltage */ +#define CDR_TRIM PHY_F(13, 6, 5) /* Clock Data Recovery setting */ +#define VCO_CTRL PHY_F(26, 7, 4) /* VCO control */ +#define VCOPLL_CTRL PHY_F(27, 2, 0) /* TxPLL VCO tuning */ +#define VCOPLL_CM PHY_F(28, 1, 0) /* TxPLL voltage */ + +#define MAX_PHY_PARAMS 7 + +struct uniphier_u3ssphy_param { + struct { + int reg_no; + int msb; + int lsb; + } field; + u8 value; +}; + +struct uniphier_u3ssphy_priv { + struct device *dev; + void __iomem *base; + struct clk *clk, *clk_ext, *clk_parent, *clk_parent_gio; + struct reset_control *rst, *rst_parent, *rst_parent_gio; + struct regulator *vbus; + const struct uniphier_u3ssphy_soc_data *data; +}; + +struct uniphier_u3ssphy_soc_data { + bool is_legacy; + int nparams; + const struct uniphier_u3ssphy_param param[MAX_PHY_PARAMS]; +}; + +static void uniphier_u3ssphy_testio_write(struct uniphier_u3ssphy_priv *priv, + u32 data) +{ + /* need to read TESTO twice after accessing TESTI */ + writel(data, priv->base + SSPHY_TESTI); + readl(priv->base + SSPHY_TESTO); + readl(priv->base + SSPHY_TESTO); +} + +static void uniphier_u3ssphy_set_param(struct uniphier_u3ssphy_priv *priv, + const struct uniphier_u3ssphy_param *p) +{ + u32 val; + u8 field_mask = GENMASK(p->field.msb, p->field.lsb); + u8 data; + + /* read previous data */ + val = FIELD_PREP(TESTI_DAT_MASK, 1); + val |= FIELD_PREP(TESTI_ADR_MASK, p->field.reg_no); + uniphier_u3ssphy_testio_write(priv, val); + val = readl(priv->base + SSPHY_TESTO); + + /* update value */ + val &= ~FIELD_PREP(TESTI_DAT_MASK, field_mask); + data = field_mask & (p->value << p->field.lsb); + val = FIELD_PREP(TESTI_DAT_MASK, data); + val |= FIELD_PREP(TESTI_ADR_MASK, p->field.reg_no); + uniphier_u3ssphy_testio_write(priv, val); + uniphier_u3ssphy_testio_write(priv, val | TESTI_WR_EN); + uniphier_u3ssphy_testio_write(priv, val); + + /* read current data as dummy */ + val = FIELD_PREP(TESTI_DAT_MASK, 1); + val |= FIELD_PREP(TESTI_ADR_MASK, p->field.reg_no); + uniphier_u3ssphy_testio_write(priv, val); + readl(priv->base + SSPHY_TESTO); +} + +static int uniphier_u3ssphy_power_on(struct phy *phy) +{ + struct uniphier_u3ssphy_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = clk_prepare_enable(priv->clk_ext); + if (ret) + return ret; + + ret = clk_prepare_enable(priv->clk); + if (ret) + goto out_clk_ext_disable; + + ret = reset_control_deassert(priv->rst); + if (ret) + goto out_clk_disable; + + if (priv->vbus) { + ret = regulator_enable(priv->vbus); + if (ret) + goto out_rst_assert; + } + + return 0; + +out_rst_assert: + reset_control_assert(priv->rst); +out_clk_disable: + clk_disable_unprepare(priv->clk); +out_clk_ext_disable: + clk_disable_unprepare(priv->clk_ext); + + return ret; +} + +static int uniphier_u3ssphy_power_off(struct phy *phy) +{ + struct uniphier_u3ssphy_priv *priv = phy_get_drvdata(phy); + + if (priv->vbus) + regulator_disable(priv->vbus); + + reset_control_assert(priv->rst); + clk_disable_unprepare(priv->clk); + clk_disable_unprepare(priv->clk_ext); + + return 0; +} + +static int uniphier_u3ssphy_init(struct phy *phy) +{ + struct uniphier_u3ssphy_priv *priv = phy_get_drvdata(phy); + int i, ret; + + ret = clk_prepare_enable(priv->clk_parent); + if (ret) + return ret; + + ret = clk_prepare_enable(priv->clk_parent_gio); + if (ret) + goto out_clk_disable; + + ret = reset_control_deassert(priv->rst_parent); + if (ret) + goto out_clk_gio_disable; + + ret = reset_control_deassert(priv->rst_parent_gio); + if (ret) + goto out_rst_assert; + + if (priv->data->is_legacy) + return 0; + + for (i = 0; i < priv->data->nparams; i++) + uniphier_u3ssphy_set_param(priv, &priv->data->param[i]); + + return 0; + +out_rst_assert: + reset_control_assert(priv->rst_parent); +out_clk_gio_disable: + clk_disable_unprepare(priv->clk_parent_gio); +out_clk_disable: + clk_disable_unprepare(priv->clk_parent); + + return ret; +} + +static int uniphier_u3ssphy_exit(struct phy *phy) +{ + struct uniphier_u3ssphy_priv *priv = phy_get_drvdata(phy); + + reset_control_assert(priv->rst_parent_gio); + reset_control_assert(priv->rst_parent); + clk_disable_unprepare(priv->clk_parent_gio); + clk_disable_unprepare(priv->clk_parent); + + return 0; +} + +static const struct phy_ops uniphier_u3ssphy_ops = { + .init = uniphier_u3ssphy_init, + .exit = uniphier_u3ssphy_exit, + .power_on = uniphier_u3ssphy_power_on, + .power_off = uniphier_u3ssphy_power_off, + .owner = THIS_MODULE, +}; + +static int uniphier_u3ssphy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct uniphier_u3ssphy_priv *priv; + struct phy_provider *phy_provider; + struct resource *res; + struct phy *phy; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->data = of_device_get_match_data(dev); + if (WARN_ON(!priv->data || + priv->data->nparams > MAX_PHY_PARAMS)) + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + if (!priv->data->is_legacy) { + priv->clk = devm_clk_get(dev, "phy"); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + priv->clk_ext = devm_clk_get(dev, "phy-ext"); + if (IS_ERR(priv->clk_ext)) { + if (PTR_ERR(priv->clk_ext) == -ENOENT) + priv->clk_ext = NULL; + else + return PTR_ERR(priv->clk_ext); + } + + priv->rst = devm_reset_control_get_shared(dev, "phy"); + if (IS_ERR(priv->rst)) + return PTR_ERR(priv->rst); + } else { + priv->clk_parent_gio = devm_clk_get(dev, "gio"); + if (IS_ERR(priv->clk_parent_gio)) + return PTR_ERR(priv->clk_parent_gio); + + priv->rst_parent_gio = + devm_reset_control_get_shared(dev, "gio"); + if (IS_ERR(priv->rst_parent_gio)) + return PTR_ERR(priv->rst_parent_gio); + } + + priv->clk_parent = devm_clk_get(dev, "link"); + if (IS_ERR(priv->clk_parent)) + return PTR_ERR(priv->clk_parent); + + priv->rst_parent = devm_reset_control_get_shared(dev, "link"); + if (IS_ERR(priv->rst_parent)) + return PTR_ERR(priv->rst_parent); + + priv->vbus = devm_regulator_get_optional(dev, "vbus"); + if (IS_ERR(priv->vbus)) { + if (PTR_ERR(priv->vbus) == -EPROBE_DEFER) + return PTR_ERR(priv->vbus); + priv->vbus = NULL; + } + + phy = devm_phy_create(dev, dev->of_node, &uniphier_u3ssphy_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + phy_set_drvdata(phy, priv); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct uniphier_u3ssphy_soc_data uniphier_pro4_data = { + .is_legacy = true, +}; + +static const struct uniphier_u3ssphy_soc_data uniphier_pxs2_data = { + .is_legacy = false, + .nparams = 7, + .param = { + { CDR_CPD_TRIM, 10 }, + { CDR_CPF_TRIM, 3 }, + { TX_PLL_TRIM, 5 }, + { BGAP_TRIM, 9 }, + { CDR_TRIM, 2 }, + { VCOPLL_CTRL, 7 }, + { VCOPLL_CM, 1 }, + }, +}; + +static const struct uniphier_u3ssphy_soc_data uniphier_ld20_data = { + .is_legacy = false, + .nparams = 3, + .param = { + { CDR_CPD_TRIM, 6 }, + { CDR_TRIM, 2 }, + { VCO_CTRL, 5 }, + }, +}; + +static const struct of_device_id uniphier_u3ssphy_match[] = { + { + .compatible = "socionext,uniphier-pro4-usb3-ssphy", + .data = &uniphier_pro4_data, + }, + { + .compatible = "socionext,uniphier-pxs2-usb3-ssphy", + .data = &uniphier_pxs2_data, + }, + { + .compatible = "socionext,uniphier-ld20-usb3-ssphy", + .data = &uniphier_ld20_data, + }, + { + .compatible = "socionext,uniphier-pxs3-usb3-ssphy", + .data = &uniphier_ld20_data, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, uniphier_u3ssphy_match); + +static struct platform_driver uniphier_u3ssphy_driver = { + .probe = uniphier_u3ssphy_probe, + .driver = { + .name = "uniphier-usb3-ssphy", + .of_match_table = uniphier_u3ssphy_match, + }, +}; + +module_platform_driver(uniphier_u3ssphy_driver); + +MODULE_AUTHOR("Kunihiko Hayashi "); +MODULE_DESCRIPTION("UniPhier SS-PHY driver for USB3 controller"); +MODULE_LICENSE("GPL v2"); From 39f68636490f1cd4dd5fd5173fb776433f6bf072 Mon Sep 17 00:00:00 2001 From: Kunihiko Hayashi Date: Wed, 22 Aug 2018 12:50:33 +0900 Subject: [PATCH 08/41] dt-bindings: phy: add DT bindings for UniPhier USB2 PHY driver Add DT bindings for PHY interface built into USB2 controller implemented on Socionext UniPhier SoCs. Signed-off-by: Kunihiko Hayashi Signed-off-by: Kishon Vijay Abraham I --- .../bindings/phy/uniphier-usb2-phy.txt | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/uniphier-usb2-phy.txt diff --git a/Documentation/devicetree/bindings/phy/uniphier-usb2-phy.txt b/Documentation/devicetree/bindings/phy/uniphier-usb2-phy.txt new file mode 100644 index 0000000000000..b43b28250cc0b --- /dev/null +++ b/Documentation/devicetree/bindings/phy/uniphier-usb2-phy.txt @@ -0,0 +1,45 @@ +Socionext UniPhier USB2 PHY + +This describes the devicetree bindings for PHY interface built into +USB2 controller implemented on Socionext UniPhier SoCs. + +Pro4 SoC has both USB2 and USB3 host controllers, however, this USB3 +controller doesn't include its own High-Speed PHY. This needs to specify +USB2 PHY instead of USB3 HS-PHY. + +Required properties: +- compatible: Should contain one of the following: + "socionext,uniphier-pro4-usb2-phy" - for Pro4 SoC + "socionext,uniphier-ld11-usb2-phy" - for LD11 SoC + +Sub-nodes: +Each PHY should be represented as a sub-node. + +Sub-nodes required properties: +- #phy-cells: Should be 0. +- reg: The number of the PHY. + +Sub-nodes optional properties: +- vbus-supply: A phandle to the regulator for USB VBUS. + +Refer to phy/phy-bindings.txt for the generic PHY binding properties. + +Example: + soc-glue@5f800000 { + ... + usb-phy { + compatible = "socionext,uniphier-ld11-usb2-phy"; + usb_phy0: phy@0 { + reg = <0>; + #phy-cells = <0>; + }; + ... + }; + }; + + usb@5a800100 { + compatible = "socionext,uniphier-ehci", "generic-ehci"; + ... + phy-names = "usb"; + phys = <&usb_phy0>; + }; From c339d3e0fb100465d644ccf84590e6f5e5ad80b9 Mon Sep 17 00:00:00 2001 From: Kunihiko Hayashi Date: Wed, 22 Aug 2018 12:50:34 +0900 Subject: [PATCH 09/41] phy: socionext: add USB2 PHY driver for UniPhier SoC Add a driver for PHY interface built into USB2 controller implemented on UniPhier SoCs. This driver supports HS-PHY for Pro4 and LD11. Signed-off-by: Kunihiko Hayashi Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/socionext/Kconfig | 13 ++ drivers/phy/socionext/Makefile | 1 + drivers/phy/socionext/phy-uniphier-usb2.c | 244 ++++++++++++++++++++++ 3 files changed, 258 insertions(+) create mode 100644 drivers/phy/socionext/phy-uniphier-usb2.c diff --git a/drivers/phy/socionext/Kconfig b/drivers/phy/socionext/Kconfig index 4a172fce92512..497ca38214521 100644 --- a/drivers/phy/socionext/Kconfig +++ b/drivers/phy/socionext/Kconfig @@ -2,6 +2,19 @@ # PHY drivers for Socionext platforms. # +config PHY_UNIPHIER_USB2 + tristate "UniPhier USB2 PHY driver" + depends on ARCH_UNIPHIER || COMPILE_TEST + depends on OF && HAS_IOMEM + select GENERIC_PHY + select MFD_SYSCON + help + Enable this to support USB PHY implemented on USB2 controller + on UniPhier SoCs. This driver provides interface to interact + with USB 2.0 PHY that is part of the UniPhier SoC. + In case of Pro4, it is necessary to specify this USB2 PHY instead + of USB3 HS-PHY. + config PHY_UNIPHIER_USB3 tristate "UniPhier USB3 PHY driver" depends on ARCH_UNIPHIER || COMPILE_TEST diff --git a/drivers/phy/socionext/Makefile b/drivers/phy/socionext/Makefile index e230fa314bbad..91e482564386d 100644 --- a/drivers/phy/socionext/Makefile +++ b/drivers/phy/socionext/Makefile @@ -3,4 +3,5 @@ # Makefile for the phy drivers. # +obj-$(CONFIG_PHY_UNIPHIER_USB2) += phy-uniphier-usb2.o obj-$(CONFIG_PHY_UNIPHIER_USB3) += phy-uniphier-usb3hs.o phy-uniphier-usb3ss.o diff --git a/drivers/phy/socionext/phy-uniphier-usb2.c b/drivers/phy/socionext/phy-uniphier-usb2.c new file mode 100644 index 0000000000000..3f2086ed4fe4f --- /dev/null +++ b/drivers/phy/socionext/phy-uniphier-usb2.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * phy-uniphier-usb2.c - PHY driver for UniPhier USB2 controller + * Copyright 2015-2018 Socionext Inc. + * Author: + * Kunihiko Hayashi + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SG_USBPHY1CTRL 0x500 +#define SG_USBPHY1CTRL2 0x504 +#define SG_USBPHY2CTRL 0x508 +#define SG_USBPHY2CTRL2 0x50c /* LD11 */ +#define SG_USBPHY12PLL 0x50c /* Pro4 */ +#define SG_USBPHY3CTRL 0x510 +#define SG_USBPHY3CTRL2 0x514 +#define SG_USBPHY4CTRL 0x518 /* Pro4 */ +#define SG_USBPHY4CTRL2 0x51c /* Pro4 */ +#define SG_USBPHY34PLL 0x51c /* Pro4 */ + +struct uniphier_u2phy_param { + u32 offset; + u32 value; +}; + +struct uniphier_u2phy_soc_data { + struct uniphier_u2phy_param config0; + struct uniphier_u2phy_param config1; +}; + +struct uniphier_u2phy_priv { + struct regmap *regmap; + struct phy *phy; + struct regulator *vbus; + const struct uniphier_u2phy_soc_data *data; + struct uniphier_u2phy_priv *next; +}; + +static int uniphier_u2phy_power_on(struct phy *phy) +{ + struct uniphier_u2phy_priv *priv = phy_get_drvdata(phy); + int ret = 0; + + if (priv->vbus) + ret = regulator_enable(priv->vbus); + + return ret; +} + +static int uniphier_u2phy_power_off(struct phy *phy) +{ + struct uniphier_u2phy_priv *priv = phy_get_drvdata(phy); + + if (priv->vbus) + regulator_disable(priv->vbus); + + return 0; +} + +static int uniphier_u2phy_init(struct phy *phy) +{ + struct uniphier_u2phy_priv *priv = phy_get_drvdata(phy); + + if (!priv->data) + return 0; + + regmap_write(priv->regmap, priv->data->config0.offset, + priv->data->config0.value); + regmap_write(priv->regmap, priv->data->config1.offset, + priv->data->config1.value); + + return 0; +} + +static struct phy *uniphier_u2phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct uniphier_u2phy_priv *priv = dev_get_drvdata(dev); + + while (priv && args->np != priv->phy->dev.of_node) + priv = priv->next; + + if (!priv) { + dev_err(dev, "Failed to find appropriate phy\n"); + return ERR_PTR(-EINVAL); + } + + return priv->phy; +} + +static const struct phy_ops uniphier_u2phy_ops = { + .init = uniphier_u2phy_init, + .power_on = uniphier_u2phy_power_on, + .power_off = uniphier_u2phy_power_off, + .owner = THIS_MODULE, +}; + +static int uniphier_u2phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *parent, *child; + struct uniphier_u2phy_priv *priv = NULL, *next = NULL; + struct phy_provider *phy_provider; + struct regmap *regmap; + const struct uniphier_u2phy_soc_data *data; + int ret, data_idx, ndatas; + + data = of_device_get_match_data(dev); + if (WARN_ON(!data)) + return -EINVAL; + + /* get number of data */ + for (ndatas = 0; data[ndatas].config0.offset; ndatas++) + ; + + parent = of_get_parent(dev->of_node); + regmap = syscon_node_to_regmap(parent); + of_node_put(parent); + if (IS_ERR(regmap)) { + dev_err(dev, "Failed to get regmap\n"); + return PTR_ERR(regmap); + } + + for_each_child_of_node(dev->of_node, child) { + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + ret = -ENOMEM; + goto out_put_child; + } + priv->regmap = regmap; + + priv->vbus = devm_regulator_get_optional(dev, "vbus"); + if (IS_ERR(priv->vbus)) { + if (PTR_ERR(priv->vbus) == -EPROBE_DEFER) { + ret = PTR_ERR(priv->vbus); + goto out_put_child; + } + priv->vbus = NULL; + } + + priv->phy = devm_phy_create(dev, child, &uniphier_u2phy_ops); + if (IS_ERR(priv->phy)) { + dev_err(dev, "Failed to create phy\n"); + ret = PTR_ERR(priv->phy); + goto out_put_child; + } + + ret = of_property_read_u32(child, "reg", &data_idx); + if (ret) { + dev_err(dev, "Failed to get reg property\n"); + goto out_put_child; + } + + if (data_idx < ndatas) + priv->data = &data[data_idx]; + else + dev_warn(dev, "No phy configuration: %s\n", + child->full_name); + + phy_set_drvdata(priv->phy, priv); + priv->next = next; + next = priv; + } + + dev_set_drvdata(dev, priv); + phy_provider = devm_of_phy_provider_register(dev, + uniphier_u2phy_xlate); + return PTR_ERR_OR_ZERO(phy_provider); + +out_put_child: + of_node_put(child); + + return ret; +} + +static const struct uniphier_u2phy_soc_data uniphier_pro4_data[] = { + { + .config0 = { SG_USBPHY1CTRL, 0x05142400 }, + .config1 = { SG_USBPHY12PLL, 0x00010010 }, + }, + { + .config0 = { SG_USBPHY2CTRL, 0x05142400 }, + .config1 = { SG_USBPHY12PLL, 0x00010010 }, + }, + { + .config0 = { SG_USBPHY3CTRL, 0x05142400 }, + .config1 = { SG_USBPHY34PLL, 0x00010010 }, + }, + { + .config0 = { SG_USBPHY4CTRL, 0x05142400 }, + .config1 = { SG_USBPHY34PLL, 0x00010010 }, + }, + { /* sentinel */ } +}; + +static const struct uniphier_u2phy_soc_data uniphier_ld11_data[] = { + { + .config0 = { SG_USBPHY1CTRL, 0x82280000 }, + .config1 = { SG_USBPHY1CTRL2, 0x00000106 }, + }, + { + .config0 = { SG_USBPHY2CTRL, 0x82280000 }, + .config1 = { SG_USBPHY2CTRL2, 0x00000106 }, + }, + { + .config0 = { SG_USBPHY3CTRL, 0x82280000 }, + .config1 = { SG_USBPHY3CTRL2, 0x00000106 }, + }, + { /* sentinel */ } +}; + +static const struct of_device_id uniphier_u2phy_match[] = { + { + .compatible = "socionext,uniphier-pro4-usb2-phy", + .data = &uniphier_pro4_data, + }, + { + .compatible = "socionext,uniphier-ld11-usb2-phy", + .data = &uniphier_ld11_data, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, uniphier_u2phy_match); + +static struct platform_driver uniphier_u2phy_driver = { + .probe = uniphier_u2phy_probe, + .driver = { + .name = "uniphier-usb2-phy", + .of_match_table = uniphier_u2phy_match, + }, +}; +module_platform_driver(uniphier_u2phy_driver); + +MODULE_AUTHOR("Kunihiko Hayashi "); +MODULE_DESCRIPTION("UniPhier PHY driver for USB2 controller"); +MODULE_LICENSE("GPL v2"); From eee0e5daa7574208f84fc24bc829ece58ef021f1 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Wed, 22 Aug 2018 00:02:20 +0200 Subject: [PATCH 10/41] phy: renesas: use SPDX identifier for Renesas drivers Use SPDX identifier for Renesas drivers. Signed-off-by: Wolfram Sang Acked-by: Yoshihiro Shimoda Reviewed-by: Simon Horman Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/renesas/phy-rcar-gen2.c | 5 +---- drivers/phy/renesas/phy-rcar-gen3-usb2.c | 5 +---- drivers/phy/renesas/phy-rcar-gen3-usb3.c | 5 +---- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/drivers/phy/renesas/phy-rcar-gen2.c b/drivers/phy/renesas/phy-rcar-gen2.c index 97d4dd6ea9247..72eeb066912d8 100644 --- a/drivers/phy/renesas/phy-rcar-gen2.c +++ b/drivers/phy/renesas/phy-rcar-gen2.c @@ -1,12 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Renesas R-Car Gen2 PHY driver * * Copyright (C) 2014 Renesas Solutions Corp. * Copyright (C) 2014 Cogent Embedded, Inc. - * - * 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. */ #include diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c index fb8f05e39cf7f..3d57ea1e1437e 100644 --- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Renesas R-Car Gen3 for USB2.0 PHY driver * @@ -6,10 +7,6 @@ * This is based on the phy-rcar-gen2 driver: * Copyright (C) 2014 Renesas Solutions Corp. * Copyright (C) 2014 Cogent Embedded, Inc. - * - * 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. */ #include diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb3.c b/drivers/phy/renesas/phy-rcar-gen3-usb3.c index 88c83c9b8ff93..566b4cf4ff383 100644 --- a/drivers/phy/renesas/phy-rcar-gen3-usb3.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb3.c @@ -1,11 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Renesas R-Car Gen3 for USB3.0 PHY driver * * Copyright (C) 2017 Renesas Electronics Corporation - * - * 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. */ #include From 528648143354e0551087adfad27c174083316ca3 Mon Sep 17 00:00:00 2001 From: zhong jiang Date: Thu, 16 Aug 2018 23:58:54 +0800 Subject: [PATCH 11/41] phy:phy-brcm-usb: Use PTR_ERR_OR_ZERO to replace the open coded version PTR_ERR_OR_ZERO has implemented the if(IS_ERR(...)) + PTR_ERR, So just replace them rather than duplicating its implement. Signed-off-by: zhong jiang Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/broadcom/phy-brcm-usb.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/phy/broadcom/phy-brcm-usb.c b/drivers/phy/broadcom/phy-brcm-usb.c index d1dab36fa5b7b..f59b1dc30399b 100644 --- a/drivers/phy/broadcom/phy-brcm-usb.c +++ b/drivers/phy/broadcom/phy-brcm-usb.c @@ -372,10 +372,8 @@ static int brcm_usb_phy_probe(struct platform_device *pdev) clk_disable(priv->usb_30_clk); phy_provider = devm_of_phy_provider_register(dev, brcm_usb_phy_xlate); - if (IS_ERR(phy_provider)) - return PTR_ERR(phy_provider); - return 0; + return PTR_ERR_OR_ZERO(phy_provider); } #ifdef CONFIG_PM_SLEEP From 9be08a27a1588d0b0143486f96c7a08f8cfadae8 Mon Sep 17 00:00:00 2001 From: zhong jiang Date: Thu, 16 Aug 2018 23:58:55 +0800 Subject: [PATCH 12/41] phy:phy-lantiq-rcu-usb2: Use PTR_ERR_OR_ZERO to replace the open coded version PTR_ERR_OR_ZERO has implemented the if(IS_ERR(...)) + PTR_ERR, So just replace them rather than duplicating its implement. Signed-off-by: zhong jiang Acked-by: Hauke Mehrtens Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/lantiq/phy-lantiq-rcu-usb2.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/phy/lantiq/phy-lantiq-rcu-usb2.c b/drivers/phy/lantiq/phy-lantiq-rcu-usb2.c index 986224fca9e91..a918c5b2c4d9d 100644 --- a/drivers/phy/lantiq/phy-lantiq-rcu-usb2.c +++ b/drivers/phy/lantiq/phy-lantiq-rcu-usb2.c @@ -196,10 +196,8 @@ static int ltq_rcu_usb2_of_parse(struct ltq_rcu_usb2_priv *priv, } priv->phy_reset = devm_reset_control_get_optional(dev, "phy"); - if (IS_ERR(priv->phy_reset)) - return PTR_ERR(priv->phy_reset); - return 0; + return PTR_ERR_OR_ZERO(priv->phy_reset); } static int ltq_rcu_usb2_phy_probe(struct platform_device *pdev) From 4e3fe1cb25adf305ee6b73d83bd1f507fc9c975c Mon Sep 17 00:00:00 2001 From: Zheng Yang Date: Fri, 7 Sep 2018 12:28:01 +0200 Subject: [PATCH 13/41] dt-bindings: add binding for Rockchip hdmi phy using an Innosilicon IP The phy is used so far in two Rockchip socs the rk3228 and the rk3328. Signed-off-by: Zheng Yang Signed-off-by: Heiko Stuebner Reviewed-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- .../bindings/phy/phy-rockchip-inno-hdmi.txt | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/phy-rockchip-inno-hdmi.txt diff --git a/Documentation/devicetree/bindings/phy/phy-rockchip-inno-hdmi.txt b/Documentation/devicetree/bindings/phy/phy-rockchip-inno-hdmi.txt new file mode 100644 index 0000000000000..710cccd5ee56e --- /dev/null +++ b/Documentation/devicetree/bindings/phy/phy-rockchip-inno-hdmi.txt @@ -0,0 +1,43 @@ +ROCKCHIP HDMI PHY WITH INNO IP BLOCK + +Required properties: + - compatible : should be one of the listed compatibles: + * "rockchip,rk3228-hdmi-phy", + * "rockchip,rk3328-hdmi-phy"; + - reg : Address and length of the hdmi phy control register set + - clocks : phandle + clock specifier for the phy clocks + - clock-names : string, clock name, must contain "sysclk" for system + control and register configuration, "refoclk" for crystal- + oscillator reference PLL clock input and "refpclk" for pclk- + based refeference PLL clock input. + - #clock-cells: should be 0. + - clock-output-names : shall be the name for the output clock. + - interrupts : phandle + interrupt specified for the hdmiphy interrupt + - #phy-cells : must be 0. See ./phy-bindings.txt for details. + +Optional properties for rk3328-hdmi-phy: + - nvmem-cells = phandle + nvmem specifier for the cpu-version efuse + - nvmem-cell-names : "cpu-version" to read the chip version, required + for adjustment to some frequency settings + +Example: + hdmi_phy: hdmi-phy@12030000 { + compatible = "rockchip,rk3228-hdmi-phy"; + reg = <0x12030000 0x10000>; + #phy-cells = <0>; + clocks = <&cru PCLK_HDMI_PHY>, <&xin24m>, <&cru DCLK_HDMIPHY>; + clock-names = "sysclk", "refoclk", "refpclk"; + #clock-cells = <0>; + clock-output-names = "hdmi_phy"; + status = "disabled"; + }; + +Then the PHY can be used in other nodes such as: + + hdmi: hdmi@200a0000 { + compatible = "rockchip,rk3228-dw-hdmi"; + ... + phys = <&hdmi_phy>; + phy-names = "hdmi"; + ... + }; From 53706a1168631fa5bf2e6d47de4647ea7e69f270 Mon Sep 17 00:00:00 2001 From: Zheng Yang Date: Fri, 7 Sep 2018 12:28:02 +0200 Subject: [PATCH 14/41] phy: add Rockchip Innosilicon hdmi phy Add a driver for the Innosilicon hdmi phy used on rk3228/rk3229 and rk3328 socs from Rockchip. Signed-off-by: Zheng Yang Signed-off-by: Heiko Stuebner Tested-by: Robin Murphy Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/rockchip/Kconfig | 8 + drivers/phy/rockchip/Makefile | 1 + drivers/phy/rockchip/phy-rockchip-inno-hdmi.c | 1277 +++++++++++++++++ 3 files changed, 1286 insertions(+) create mode 100644 drivers/phy/rockchip/phy-rockchip-inno-hdmi.c diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig index 0e15119ddfc68..990204a46eb6f 100644 --- a/drivers/phy/rockchip/Kconfig +++ b/drivers/phy/rockchip/Kconfig @@ -15,6 +15,14 @@ config PHY_ROCKCHIP_EMMC help Enable this to support the Rockchip EMMC PHY. +config PHY_ROCKCHIP_INNO_HDMI + tristate "Rockchip INNO HDMI PHY Driver" + depends on (ARCH_ROCKCHIP || COMPILE_TEST) && OF + depends on COMMON_CLK + select GENERIC_PHY + help + Enable this to support the Rockchip Innosilicon HDMI PHY. + config PHY_ROCKCHIP_INNO_USB2 tristate "Rockchip INNO USB2PHY Driver" depends on (ARCH_ROCKCHIP || COMPILE_TEST) && OF diff --git a/drivers/phy/rockchip/Makefile b/drivers/phy/rockchip/Makefile index 7f149d9890466..fd21cbaf40dd6 100644 --- a/drivers/phy/rockchip/Makefile +++ b/drivers/phy/rockchip/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_PHY_ROCKCHIP_DP) += phy-rockchip-dp.o obj-$(CONFIG_PHY_ROCKCHIP_EMMC) += phy-rockchip-emmc.o +obj-$(CONFIG_PHY_ROCKCHIP_INNO_HDMI) += phy-rockchip-inno-hdmi.o obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2) += phy-rockchip-inno-usb2.o obj-$(CONFIG_PHY_ROCKCHIP_PCIE) += phy-rockchip-pcie.o obj-$(CONFIG_PHY_ROCKCHIP_TYPEC) += phy-rockchip-typec.o diff --git a/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c b/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c new file mode 100644 index 0000000000000..b10a84cab4a7c --- /dev/null +++ b/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c @@ -0,0 +1,1277 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2017 Rockchip Electronics Co. Ltd. + * + * Author: Zheng Yang + * Heiko Stuebner + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define UPDATE(x, h, l) (((x) << (l)) & GENMASK((h), (l))) + +/* REG: 0x00 */ +#define RK3228_PRE_PLL_REFCLK_SEL_PCLK BIT(0) +/* REG: 0x01 */ +#define RK3228_BYPASS_RXSENSE_EN BIT(2) +#define RK3228_BYPASS_PWRON_EN BIT(1) +#define RK3228_BYPASS_PLLPD_EN BIT(0) +/* REG: 0x02 */ +#define RK3228_BYPASS_PDATA_EN BIT(4) +#define RK3228_PDATAEN_DISABLE BIT(0) +/* REG: 0x03 */ +#define RK3228_BYPASS_AUTO_TERM_RES_CAL BIT(7) +#define RK3228_AUTO_TERM_RES_CAL_SPEED_14_8(x) UPDATE(x, 6, 0) +/* REG: 0x04 */ +#define RK3228_AUTO_TERM_RES_CAL_SPEED_7_0(x) UPDATE(x, 7, 0) +/* REG: 0xaa */ +#define RK3228_POST_PLL_CTRL_MANUAL BIT(0) +/* REG: 0xe0 */ +#define RK3228_POST_PLL_POWER_DOWN BIT(5) +#define RK3228_PRE_PLL_POWER_DOWN BIT(4) +#define RK3228_RXSENSE_CLK_CH_ENABLE BIT(3) +#define RK3228_RXSENSE_DATA_CH2_ENABLE BIT(2) +#define RK3228_RXSENSE_DATA_CH1_ENABLE BIT(1) +#define RK3228_RXSENSE_DATA_CH0_ENABLE BIT(0) +/* REG: 0xe1 */ +#define RK3228_BANDGAP_ENABLE BIT(4) +#define RK3228_TMDS_DRIVER_ENABLE GENMASK(3, 0) +/* REG: 0xe2 */ +#define RK3228_PRE_PLL_FB_DIV_8_MASK BIT(7) +#define RK3228_PRE_PLL_FB_DIV_8(x) UPDATE((x) >> 8, 7, 7) +#define RK3228_PCLK_VCO_DIV_5_MASK BIT(5) +#define RK3228_PCLK_VCO_DIV_5(x) UPDATE(x, 5, 5) +#define RK3228_PRE_PLL_PRE_DIV_MASK GENMASK(4, 0) +#define RK3228_PRE_PLL_PRE_DIV(x) UPDATE(x, 4, 0) +/* REG: 0xe3 */ +#define RK3228_PRE_PLL_FB_DIV_7_0(x) UPDATE(x, 7, 0) +/* REG: 0xe4 */ +#define RK3228_PRE_PLL_PCLK_DIV_B_MASK GENMASK(6, 5) +#define RK3228_PRE_PLL_PCLK_DIV_B_SHIFT 5 +#define RK3228_PRE_PLL_PCLK_DIV_B(x) UPDATE(x, 6, 5) +#define RK3228_PRE_PLL_PCLK_DIV_A_MASK GENMASK(4, 0) +#define RK3228_PRE_PLL_PCLK_DIV_A(x) UPDATE(x, 4, 0) +/* REG: 0xe5 */ +#define RK3228_PRE_PLL_PCLK_DIV_C_MASK GENMASK(6, 5) +#define RK3228_PRE_PLL_PCLK_DIV_C(x) UPDATE(x, 6, 5) +#define RK3228_PRE_PLL_PCLK_DIV_D_MASK GENMASK(4, 0) +#define RK3228_PRE_PLL_PCLK_DIV_D(x) UPDATE(x, 4, 0) +/* REG: 0xe6 */ +#define RK3228_PRE_PLL_TMDSCLK_DIV_C_MASK GENMASK(5, 4) +#define RK3228_PRE_PLL_TMDSCLK_DIV_C(x) UPDATE(x, 5, 4) +#define RK3228_PRE_PLL_TMDSCLK_DIV_A_MASK GENMASK(3, 2) +#define RK3228_PRE_PLL_TMDSCLK_DIV_A(x) UPDATE(x, 3, 2) +#define RK3228_PRE_PLL_TMDSCLK_DIV_B_MASK GENMASK(1, 0) +#define RK3228_PRE_PLL_TMDSCLK_DIV_B(x) UPDATE(x, 1, 0) +/* REG: 0xe8 */ +#define RK3228_PRE_PLL_LOCK_STATUS BIT(0) +/* REG: 0xe9 */ +#define RK3228_POST_PLL_POST_DIV_ENABLE UPDATE(3, 7, 6) +#define RK3228_POST_PLL_PRE_DIV_MASK GENMASK(4, 0) +#define RK3228_POST_PLL_PRE_DIV(x) UPDATE(x, 4, 0) +/* REG: 0xea */ +#define RK3228_POST_PLL_FB_DIV_7_0(x) UPDATE(x, 7, 0) +/* REG: 0xeb */ +#define RK3228_POST_PLL_FB_DIV_8_MASK BIT(7) +#define RK3228_POST_PLL_FB_DIV_8(x) UPDATE((x) >> 8, 7, 7) +#define RK3228_POST_PLL_POST_DIV_MASK GENMASK(5, 4) +#define RK3228_POST_PLL_POST_DIV(x) UPDATE(x, 5, 4) +#define RK3228_POST_PLL_LOCK_STATUS BIT(0) +/* REG: 0xee */ +#define RK3228_TMDS_CH_TA_ENABLE GENMASK(7, 4) +/* REG: 0xef */ +#define RK3228_TMDS_CLK_CH_TA(x) UPDATE(x, 7, 6) +#define RK3228_TMDS_DATA_CH2_TA(x) UPDATE(x, 5, 4) +#define RK3228_TMDS_DATA_CH1_TA(x) UPDATE(x, 3, 2) +#define RK3228_TMDS_DATA_CH0_TA(x) UPDATE(x, 1, 0) +/* REG: 0xf0 */ +#define RK3228_TMDS_DATA_CH2_PRE_EMPHASIS_MASK GENMASK(5, 4) +#define RK3228_TMDS_DATA_CH2_PRE_EMPHASIS(x) UPDATE(x, 5, 4) +#define RK3228_TMDS_DATA_CH1_PRE_EMPHASIS_MASK GENMASK(3, 2) +#define RK3228_TMDS_DATA_CH1_PRE_EMPHASIS(x) UPDATE(x, 3, 2) +#define RK3228_TMDS_DATA_CH0_PRE_EMPHASIS_MASK GENMASK(1, 0) +#define RK3228_TMDS_DATA_CH0_PRE_EMPHASIS(x) UPDATE(x, 1, 0) +/* REG: 0xf1 */ +#define RK3228_TMDS_CLK_CH_OUTPUT_SWING(x) UPDATE(x, 7, 4) +#define RK3228_TMDS_DATA_CH2_OUTPUT_SWING(x) UPDATE(x, 3, 0) +/* REG: 0xf2 */ +#define RK3228_TMDS_DATA_CH1_OUTPUT_SWING(x) UPDATE(x, 7, 4) +#define RK3228_TMDS_DATA_CH0_OUTPUT_SWING(x) UPDATE(x, 3, 0) + +/* REG: 0x01 */ +#define RK3328_BYPASS_RXSENSE_EN BIT(2) +#define RK3328_BYPASS_POWERON_EN BIT(1) +#define RK3328_BYPASS_PLLPD_EN BIT(0) +/* REG: 0x02 */ +#define RK3328_INT_POL_HIGH BIT(7) +#define RK3328_BYPASS_PDATA_EN BIT(4) +#define RK3328_PDATA_EN BIT(0) +/* REG:0x05 */ +#define RK3328_INT_TMDS_CLK(x) UPDATE(x, 7, 4) +#define RK3328_INT_TMDS_D2(x) UPDATE(x, 3, 0) +/* REG:0x07 */ +#define RK3328_INT_TMDS_D1(x) UPDATE(x, 7, 4) +#define RK3328_INT_TMDS_D0(x) UPDATE(x, 3, 0) +/* for all RK3328_INT_TMDS_*, ESD_DET as defined in 0xc8-0xcb */ +#define RK3328_INT_AGND_LOW_PULSE_LOCKED BIT(3) +#define RK3328_INT_RXSENSE_LOW_PULSE_LOCKED BIT(2) +#define RK3328_INT_VSS_AGND_ESD_DET BIT(1) +#define RK3328_INT_AGND_VSS_ESD_DET BIT(0) +/* REG: 0xa0 */ +#define RK3328_PCLK_VCO_DIV_5_MASK BIT(1) +#define RK3328_PCLK_VCO_DIV_5(x) UPDATE(x, 1, 1) +#define RK3328_PRE_PLL_POWER_DOWN BIT(0) +/* REG: 0xa1 */ +#define RK3328_PRE_PLL_PRE_DIV_MASK GENMASK(5, 0) +#define RK3328_PRE_PLL_PRE_DIV(x) UPDATE(x, 5, 0) +/* REG: 0xa2 */ +/* unset means center spread */ +#define RK3328_SPREAD_SPECTRUM_MOD_DOWN BIT(7) +#define RK3328_SPREAD_SPECTRUM_MOD_DISABLE BIT(6) +#define RK3328_PRE_PLL_FRAC_DIV_DISABLE UPDATE(3, 5, 4) +#define RK3328_PRE_PLL_FB_DIV_11_8_MASK GENMASK(3, 0) +#define RK3328_PRE_PLL_FB_DIV_11_8(x) UPDATE((x) >> 8, 3, 0) +/* REG: 0xa3 */ +#define RK3328_PRE_PLL_FB_DIV_7_0(x) UPDATE(x, 7, 0) +/* REG: 0xa4*/ +#define RK3328_PRE_PLL_TMDSCLK_DIV_C_MASK GENMASK(1, 0) +#define RK3328_PRE_PLL_TMDSCLK_DIV_C(x) UPDATE(x, 1, 0) +#define RK3328_PRE_PLL_TMDSCLK_DIV_B_MASK GENMASK(3, 2) +#define RK3328_PRE_PLL_TMDSCLK_DIV_B(x) UPDATE(x, 3, 2) +#define RK3328_PRE_PLL_TMDSCLK_DIV_A_MASK GENMASK(5, 4) +#define RK3328_PRE_PLL_TMDSCLK_DIV_A(x) UPDATE(x, 5, 4) +/* REG: 0xa5 */ +#define RK3328_PRE_PLL_PCLK_DIV_B_SHIFT 5 +#define RK3328_PRE_PLL_PCLK_DIV_B_MASK GENMASK(6, 5) +#define RK3328_PRE_PLL_PCLK_DIV_B(x) UPDATE(x, 6, 5) +#define RK3328_PRE_PLL_PCLK_DIV_A_MASK GENMASK(4, 0) +#define RK3328_PRE_PLL_PCLK_DIV_A(x) UPDATE(x, 4, 0) +/* REG: 0xa6 */ +#define RK3328_PRE_PLL_PCLK_DIV_C_SHIFT 5 +#define RK3328_PRE_PLL_PCLK_DIV_C_MASK GENMASK(6, 5) +#define RK3328_PRE_PLL_PCLK_DIV_C(x) UPDATE(x, 6, 5) +#define RK3328_PRE_PLL_PCLK_DIV_D_MASK GENMASK(4, 0) +#define RK3328_PRE_PLL_PCLK_DIV_D(x) UPDATE(x, 4, 0) +/* REG: 0xa9 */ +#define RK3328_PRE_PLL_LOCK_STATUS BIT(0) +/* REG: 0xaa */ +#define RK3328_POST_PLL_POST_DIV_ENABLE GENMASK(3, 2) +#define RK3328_POST_PLL_REFCLK_SEL_TMDS BIT(1) +#define RK3328_POST_PLL_POWER_DOWN BIT(0) +/* REG:0xab */ +#define RK3328_POST_PLL_FB_DIV_8(x) UPDATE((x) >> 8, 7, 7) +#define RK3328_POST_PLL_PRE_DIV(x) UPDATE(x, 4, 0) +/* REG: 0xac */ +#define RK3328_POST_PLL_FB_DIV_7_0(x) UPDATE(x, 7, 0) +/* REG: 0xad */ +#define RK3328_POST_PLL_POST_DIV_MASK GENMASK(1, 0) +#define RK3328_POST_PLL_POST_DIV_2 0x0 +#define RK3328_POST_PLL_POST_DIV_4 0x1 +#define RK3328_POST_PLL_POST_DIV_8 0x3 +/* REG: 0xaf */ +#define RK3328_POST_PLL_LOCK_STATUS BIT(0) +/* REG: 0xb0 */ +#define RK3328_BANDGAP_ENABLE BIT(2) +/* REG: 0xb2 */ +#define RK3328_TMDS_CLK_DRIVER_EN BIT(3) +#define RK3328_TMDS_D2_DRIVER_EN BIT(2) +#define RK3328_TMDS_D1_DRIVER_EN BIT(1) +#define RK3328_TMDS_D0_DRIVER_EN BIT(0) +#define RK3328_TMDS_DRIVER_ENABLE (RK3328_TMDS_CLK_DRIVER_EN | \ + RK3328_TMDS_D2_DRIVER_EN | \ + RK3328_TMDS_D1_DRIVER_EN | \ + RK3328_TMDS_D0_DRIVER_EN) +/* REG:0xc5 */ +#define RK3328_BYPASS_TERM_RESISTOR_CALIB BIT(7) +#define RK3328_TERM_RESISTOR_CALIB_SPEED_14_8(x) UPDATE((x) >> 8, 6, 0) +/* REG:0xc6 */ +#define RK3328_TERM_RESISTOR_CALIB_SPEED_7_0(x) UPDATE(x, 7, 9) +/* REG:0xc7 */ +#define RK3328_TERM_RESISTOR_50 UPDATE(0, 2, 1) +#define RK3328_TERM_RESISTOR_62_5 UPDATE(1, 2, 1) +#define RK3328_TERM_RESISTOR_75 UPDATE(2, 2, 1) +#define RK3328_TERM_RESISTOR_100 UPDATE(3, 2, 1) +/* REG 0xc8 - 0xcb */ +#define RK3328_ESD_DETECT_MASK GENMASK(7, 6) +#define RK3328_ESD_DETECT_340MV (0x0 << 6) +#define RK3328_ESD_DETECT_280MV (0x1 << 6) +#define RK3328_ESD_DETECT_260MV (0x2 << 6) +#define RK3328_ESD_DETECT_240MV (0x3 << 6) +/* resistors can be used in parallel */ +#define RK3328_TMDS_TERM_RESIST_MASK GENMASK(5, 0) +#define RK3328_TMDS_TERM_RESIST_75 BIT(5) +#define RK3328_TMDS_TERM_RESIST_150 BIT(4) +#define RK3328_TMDS_TERM_RESIST_300 BIT(3) +#define RK3328_TMDS_TERM_RESIST_600 BIT(2) +#define RK3328_TMDS_TERM_RESIST_1000 BIT(1) +#define RK3328_TMDS_TERM_RESIST_2000 BIT(0) +/* REG: 0xd1 */ +#define RK3328_PRE_PLL_FRAC_DIV_23_16(x) UPDATE((x) >> 16, 7, 0) +/* REG: 0xd2 */ +#define RK3328_PRE_PLL_FRAC_DIV_15_8(x) UPDATE((x) >> 8, 7, 0) +/* REG: 0xd3 */ +#define RK3328_PRE_PLL_FRAC_DIV_7_0(x) UPDATE(x, 7, 0) + +struct inno_hdmi_phy_drv_data; + +struct inno_hdmi_phy { + struct device *dev; + struct regmap *regmap; + int irq; + + struct phy *phy; + struct clk *sysclk; + struct clk *refoclk; + struct clk *refpclk; + + /* platform data */ + const struct inno_hdmi_phy_drv_data *plat_data; + int chip_version; + + /* clk provider */ + struct clk_hw hw; + struct clk *phyclk; + unsigned long pixclock; +}; + +struct pre_pll_config { + unsigned long pixclock; + unsigned long tmdsclock; + u8 prediv; + u16 fbdiv; + u8 tmds_div_a; + u8 tmds_div_b; + u8 tmds_div_c; + u8 pclk_div_a; + u8 pclk_div_b; + u8 pclk_div_c; + u8 pclk_div_d; + u8 vco_div_5_en; + u32 fracdiv; +}; + +struct post_pll_config { + unsigned long tmdsclock; + u8 prediv; + u16 fbdiv; + u8 postdiv; + u8 version; +}; + +struct phy_config { + unsigned long tmdsclock; + u8 regs[14]; +}; + +struct inno_hdmi_phy_ops { + int (*init)(struct inno_hdmi_phy *inno); + int (*power_on)(struct inno_hdmi_phy *inno, + const struct post_pll_config *cfg, + const struct phy_config *phy_cfg); + void (*power_off)(struct inno_hdmi_phy *inno); +}; + +struct inno_hdmi_phy_drv_data { + const struct inno_hdmi_phy_ops *ops; + const struct clk_ops *clk_ops; + const struct phy_config *phy_cfg_table; +}; + +static const struct pre_pll_config pre_pll_cfg_table[] = { + { 27000000, 27000000, 1, 90, 3, 2, 2, 10, 3, 3, 4, 0, 0}, + { 27000000, 33750000, 1, 90, 1, 3, 3, 10, 3, 3, 4, 0, 0}, + { 40000000, 40000000, 1, 80, 2, 2, 2, 12, 2, 2, 2, 0, 0}, + { 59341000, 59341000, 1, 98, 3, 1, 2, 1, 3, 3, 4, 0, 0xE6AE6B}, + { 59400000, 59400000, 1, 99, 3, 1, 1, 1, 3, 3, 4, 0, 0}, + { 59341000, 74176250, 1, 98, 0, 3, 3, 1, 3, 3, 4, 0, 0xE6AE6B}, + { 59400000, 74250000, 1, 99, 1, 2, 2, 1, 3, 3, 4, 0, 0}, + { 74176000, 74176000, 1, 98, 1, 2, 2, 1, 2, 3, 4, 0, 0xE6AE6B}, + { 74250000, 74250000, 1, 99, 1, 2, 2, 1, 2, 3, 4, 0, 0}, + { 74176000, 92720000, 4, 494, 1, 2, 2, 1, 3, 3, 4, 0, 0x816817}, + { 74250000, 92812500, 4, 495, 1, 2, 2, 1, 3, 3, 4, 0, 0}, + {148352000, 148352000, 1, 98, 1, 1, 1, 1, 2, 2, 2, 0, 0xE6AE6B}, + {148500000, 148500000, 1, 99, 1, 1, 1, 1, 2, 2, 2, 0, 0}, + {148352000, 185440000, 4, 494, 0, 2, 2, 1, 3, 2, 2, 0, 0x816817}, + {148500000, 185625000, 4, 495, 0, 2, 2, 1, 3, 2, 2, 0, 0}, + {296703000, 296703000, 1, 98, 0, 1, 1, 1, 0, 2, 2, 0, 0xE6AE6B}, + {297000000, 297000000, 1, 99, 0, 1, 1, 1, 0, 2, 2, 0, 0}, + {296703000, 370878750, 4, 494, 1, 2, 0, 1, 3, 1, 1, 0, 0x816817}, + {297000000, 371250000, 4, 495, 1, 2, 0, 1, 3, 1, 1, 0, 0}, + {593407000, 296703500, 1, 98, 0, 1, 1, 1, 0, 2, 1, 0, 0xE6AE6B}, + {594000000, 297000000, 1, 99, 0, 1, 1, 1, 0, 2, 1, 0, 0}, + {593407000, 370879375, 4, 494, 1, 2, 0, 1, 3, 1, 1, 1, 0x816817}, + {594000000, 371250000, 4, 495, 1, 2, 0, 1, 3, 1, 1, 1, 0}, + {593407000, 593407000, 1, 98, 0, 2, 0, 1, 0, 1, 1, 0, 0xE6AE6B}, + {594000000, 594000000, 1, 99, 0, 2, 0, 1, 0, 1, 1, 0, 0}, + { /* sentinel */ } +}; + +static const struct post_pll_config post_pll_cfg_table[] = { + {33750000, 1, 40, 8, 1}, + {33750000, 1, 80, 8, 2}, + {74250000, 1, 40, 8, 1}, + {74250000, 18, 80, 8, 2}, + {148500000, 2, 40, 4, 3}, + {297000000, 4, 40, 2, 3}, + {594000000, 8, 40, 1, 3}, + { /* sentinel */ } +}; + +/* phy tuning values for an undocumented set of registers */ +static const struct phy_config rk3228_phy_cfg[] = { + { 165000000, { + 0xaa, 0x00, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + }, + }, { + 340000000, { + 0xaa, 0x15, 0x6a, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + }, + }, { + 594000000, { + 0xaa, 0x15, 0x7a, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + }, + }, { /* sentinel */ }, +}; + +/* phy tuning values for an undocumented set of registers */ +static const struct phy_config rk3328_phy_cfg[] = { + { 165000000, { + 0x07, 0x0a, 0x0a, 0x0a, 0x00, 0x00, 0x08, 0x08, 0x08, + 0x00, 0xac, 0xcc, 0xcc, 0xcc, + }, + }, { + 340000000, { + 0x0b, 0x0d, 0x0d, 0x0d, 0x07, 0x15, 0x08, 0x08, 0x08, + 0x3f, 0xac, 0xcc, 0xcd, 0xdd, + }, + }, { + 594000000, { + 0x10, 0x1a, 0x1a, 0x1a, 0x07, 0x15, 0x08, 0x08, 0x08, + 0x00, 0xac, 0xcc, 0xcc, 0xcc, + }, + }, { /* sentinel */ }, +}; + +static inline struct inno_hdmi_phy *to_inno_hdmi_phy(struct clk_hw *hw) +{ + return container_of(hw, struct inno_hdmi_phy, hw); +} + +/* + * The register description of the IP block does not use any distinct names + * but instead the databook simply numbers the registers in one-increments. + * As the registers are obviously 32bit sized, the inno_* functions + * translate the databook register names to the actual registers addresses. + */ +static inline void inno_write(struct inno_hdmi_phy *inno, u32 reg, u8 val) +{ + regmap_write(inno->regmap, reg * 4, val); +} + +static inline u8 inno_read(struct inno_hdmi_phy *inno, u32 reg) +{ + u32 val; + + regmap_read(inno->regmap, reg * 4, &val); + + return val; +} + +static inline void inno_update_bits(struct inno_hdmi_phy *inno, u8 reg, + u8 mask, u8 val) +{ + regmap_update_bits(inno->regmap, reg * 4, mask, val); +} + +#define inno_poll(inno, reg, val, cond, sleep_us, timeout_us) \ + regmap_read_poll_timeout((inno)->regmap, (reg) * 4, val, cond, \ + sleep_us, timeout_us) + +static unsigned long inno_hdmi_phy_get_tmdsclk(struct inno_hdmi_phy *inno, + unsigned long rate) +{ + int bus_width = phy_get_bus_width(inno->phy); + + switch (bus_width) { + case 4: + case 5: + case 6: + case 10: + case 12: + case 16: + return (u64)rate * bus_width / 8; + default: + return rate; + } +} + +static irqreturn_t inno_hdmi_phy_rk3328_hardirq(int irq, void *dev_id) +{ + struct inno_hdmi_phy *inno = dev_id; + int intr_stat1, intr_stat2, intr_stat3; + + intr_stat1 = inno_read(inno, 0x04); + intr_stat2 = inno_read(inno, 0x06); + intr_stat3 = inno_read(inno, 0x08); + + if (intr_stat1) + inno_write(inno, 0x04, intr_stat1); + if (intr_stat2) + inno_write(inno, 0x06, intr_stat2); + if (intr_stat3) + inno_write(inno, 0x08, intr_stat3); + + if (intr_stat1 || intr_stat2 || intr_stat3) + return IRQ_WAKE_THREAD; + + return IRQ_HANDLED; +} + +static irqreturn_t inno_hdmi_phy_rk3328_irq(int irq, void *dev_id) +{ + struct inno_hdmi_phy *inno = dev_id; + + inno_update_bits(inno, 0x02, RK3328_PDATA_EN, 0); + usleep_range(10, 20); + inno_update_bits(inno, 0x02, RK3328_PDATA_EN, RK3328_PDATA_EN); + + return IRQ_HANDLED; +} + +static int inno_hdmi_phy_power_on(struct phy *phy) +{ + struct inno_hdmi_phy *inno = phy_get_drvdata(phy); + const struct post_pll_config *cfg = post_pll_cfg_table; + const struct phy_config *phy_cfg = inno->plat_data->phy_cfg_table; + unsigned long tmdsclock = inno_hdmi_phy_get_tmdsclk(inno, + inno->pixclock); + int ret; + + if (!tmdsclock) { + dev_err(inno->dev, "TMDS clock is zero!\n"); + return -EINVAL; + } + + if (!inno->plat_data->ops->power_on) + return -EINVAL; + + for (; cfg->tmdsclock != 0; cfg++) + if (tmdsclock <= cfg->tmdsclock && + cfg->version & inno->chip_version) + break; + + for (; phy_cfg->tmdsclock != 0; phy_cfg++) + if (tmdsclock <= phy_cfg->tmdsclock) + break; + + if (cfg->tmdsclock == 0 || phy_cfg->tmdsclock == 0) + return -EINVAL; + + dev_dbg(inno->dev, "Inno HDMI PHY Power On\n"); + + ret = clk_prepare_enable(inno->phyclk); + if (ret) + return ret; + + ret = inno->plat_data->ops->power_on(inno, cfg, phy_cfg); + if (ret) { + clk_disable_unprepare(inno->phyclk); + return ret; + } + + return 0; +} + +static int inno_hdmi_phy_power_off(struct phy *phy) +{ + struct inno_hdmi_phy *inno = phy_get_drvdata(phy); + + if (!inno->plat_data->ops->power_off) + return -EINVAL; + + inno->plat_data->ops->power_off(inno); + + clk_disable_unprepare(inno->phyclk); + + dev_dbg(inno->dev, "Inno HDMI PHY Power Off\n"); + + return 0; +} + +static const struct phy_ops inno_hdmi_phy_ops = { + .owner = THIS_MODULE, + .power_on = inno_hdmi_phy_power_on, + .power_off = inno_hdmi_phy_power_off, +}; + +static const +struct pre_pll_config *inno_hdmi_phy_get_pre_pll_cfg(struct inno_hdmi_phy *inno, + unsigned long rate) +{ + const struct pre_pll_config *cfg = pre_pll_cfg_table; + unsigned long tmdsclock = inno_hdmi_phy_get_tmdsclk(inno, rate); + + for (; cfg->pixclock != 0; cfg++) + if (cfg->pixclock == rate && cfg->tmdsclock == tmdsclock) + break; + + if (cfg->pixclock == 0) + return ERR_PTR(-EINVAL); + + return cfg; +} + +static int inno_hdmi_phy_rk3228_clk_is_prepared(struct clk_hw *hw) +{ + struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw); + u8 status; + + status = inno_read(inno, 0xe0) & RK3228_PRE_PLL_POWER_DOWN; + return status ? 0 : 1; +} + +static int inno_hdmi_phy_rk3228_clk_prepare(struct clk_hw *hw) +{ + struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw); + + inno_update_bits(inno, 0xe0, RK3228_PRE_PLL_POWER_DOWN, 0); + return 0; +} + +static void inno_hdmi_phy_rk3228_clk_unprepare(struct clk_hw *hw) +{ + struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw); + + inno_update_bits(inno, 0xe0, RK3228_PRE_PLL_POWER_DOWN, + RK3228_PRE_PLL_POWER_DOWN); +} + +static +unsigned long inno_hdmi_phy_rk3228_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw); + u8 nd, no_a, no_b, no_d; + u64 vco; + u16 nf; + + nd = inno_read(inno, 0xe2) & RK3228_PRE_PLL_PRE_DIV_MASK; + nf = (inno_read(inno, 0xe2) & RK3228_PRE_PLL_FB_DIV_8_MASK) << 1; + nf |= inno_read(inno, 0xe3); + vco = parent_rate * nf; + + if (inno_read(inno, 0xe2) & RK3228_PCLK_VCO_DIV_5_MASK) { + do_div(vco, nd * 5); + } else { + no_a = inno_read(inno, 0xe4) & RK3228_PRE_PLL_PCLK_DIV_A_MASK; + if (!no_a) + no_a = 1; + no_b = inno_read(inno, 0xe4) & RK3228_PRE_PLL_PCLK_DIV_B_MASK; + no_b >>= RK3228_PRE_PLL_PCLK_DIV_B_SHIFT; + no_b += 2; + no_d = inno_read(inno, 0xe5) & RK3228_PRE_PLL_PCLK_DIV_D_MASK; + + do_div(vco, (nd * (no_a == 1 ? no_b : no_a) * no_d * 2)); + } + + inno->pixclock = vco; + + dev_dbg(inno->dev, "%s rate %lu\n", __func__, inno->pixclock); + + return vco; +} + +static long inno_hdmi_phy_rk3228_clk_round_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long *parent_rate) +{ + const struct pre_pll_config *cfg = pre_pll_cfg_table; + + for (; cfg->pixclock != 0; cfg++) + if (cfg->pixclock == rate && !cfg->fracdiv) + break; + + if (cfg->pixclock == 0) + return -EINVAL; + + return cfg->pixclock; +} + +static int inno_hdmi_phy_rk3228_clk_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw); + const struct pre_pll_config *cfg = pre_pll_cfg_table; + unsigned long tmdsclock = inno_hdmi_phy_get_tmdsclk(inno, rate); + u32 v; + int ret; + + dev_dbg(inno->dev, "%s rate %lu tmdsclk %lu\n", + __func__, rate, tmdsclock); + + cfg = inno_hdmi_phy_get_pre_pll_cfg(inno, rate); + if (IS_ERR(cfg)) + return PTR_ERR(cfg); + + /* Power down PRE-PLL */ + inno_update_bits(inno, 0xe0, RK3228_PRE_PLL_POWER_DOWN, + RK3228_PRE_PLL_POWER_DOWN); + + inno_update_bits(inno, 0xe2, RK3228_PRE_PLL_FB_DIV_8_MASK | + RK3228_PCLK_VCO_DIV_5_MASK | + RK3228_PRE_PLL_PRE_DIV_MASK, + RK3228_PRE_PLL_FB_DIV_8(cfg->fbdiv) | + RK3228_PCLK_VCO_DIV_5(cfg->vco_div_5_en) | + RK3228_PRE_PLL_PRE_DIV(cfg->prediv)); + inno_write(inno, 0xe3, RK3228_PRE_PLL_FB_DIV_7_0(cfg->fbdiv)); + inno_update_bits(inno, 0xe4, RK3228_PRE_PLL_PCLK_DIV_B_MASK | + RK3228_PRE_PLL_PCLK_DIV_A_MASK, + RK3228_PRE_PLL_PCLK_DIV_B(cfg->pclk_div_b) | + RK3228_PRE_PLL_PCLK_DIV_A(cfg->pclk_div_a)); + inno_update_bits(inno, 0xe5, RK3228_PRE_PLL_PCLK_DIV_C_MASK | + RK3228_PRE_PLL_PCLK_DIV_D_MASK, + RK3228_PRE_PLL_PCLK_DIV_C(cfg->pclk_div_c) | + RK3228_PRE_PLL_PCLK_DIV_D(cfg->pclk_div_d)); + inno_update_bits(inno, 0xe6, RK3228_PRE_PLL_TMDSCLK_DIV_C_MASK | + RK3228_PRE_PLL_TMDSCLK_DIV_A_MASK | + RK3228_PRE_PLL_TMDSCLK_DIV_B_MASK, + RK3228_PRE_PLL_TMDSCLK_DIV_C(cfg->tmds_div_c) | + RK3228_PRE_PLL_TMDSCLK_DIV_A(cfg->tmds_div_a) | + RK3228_PRE_PLL_TMDSCLK_DIV_B(cfg->tmds_div_b)); + + /* Power up PRE-PLL */ + inno_update_bits(inno, 0xe0, RK3228_PRE_PLL_POWER_DOWN, 0); + + /* Wait for Pre-PLL lock */ + ret = inno_poll(inno, 0xe8, v, v & RK3228_PRE_PLL_LOCK_STATUS, + 100, 100000); + if (ret) { + dev_err(inno->dev, "Pre-PLL locking failed\n"); + return ret; + } + + inno->pixclock = rate; + + return 0; +} + +static const struct clk_ops inno_hdmi_phy_rk3228_clk_ops = { + .prepare = inno_hdmi_phy_rk3228_clk_prepare, + .unprepare = inno_hdmi_phy_rk3228_clk_unprepare, + .is_prepared = inno_hdmi_phy_rk3228_clk_is_prepared, + .recalc_rate = inno_hdmi_phy_rk3228_clk_recalc_rate, + .round_rate = inno_hdmi_phy_rk3228_clk_round_rate, + .set_rate = inno_hdmi_phy_rk3228_clk_set_rate, +}; + +static int inno_hdmi_phy_rk3328_clk_is_prepared(struct clk_hw *hw) +{ + struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw); + u8 status; + + status = inno_read(inno, 0xa0) & RK3328_PRE_PLL_POWER_DOWN; + return status ? 0 : 1; +} + +static int inno_hdmi_phy_rk3328_clk_prepare(struct clk_hw *hw) +{ + struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw); + + inno_update_bits(inno, 0xa0, RK3328_PRE_PLL_POWER_DOWN, 0); + return 0; +} + +static void inno_hdmi_phy_rk3328_clk_unprepare(struct clk_hw *hw) +{ + struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw); + + inno_update_bits(inno, 0xa0, RK3328_PRE_PLL_POWER_DOWN, + RK3328_PRE_PLL_POWER_DOWN); +} + +static +unsigned long inno_hdmi_phy_rk3328_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw); + unsigned long frac; + u8 nd, no_a, no_b, no_c, no_d; + u64 vco; + u16 nf; + + nd = inno_read(inno, 0xa1) & RK3328_PRE_PLL_PRE_DIV_MASK; + nf = ((inno_read(inno, 0xa2) & RK3328_PRE_PLL_FB_DIV_11_8_MASK) << 8); + nf |= inno_read(inno, 0xa3); + vco = parent_rate * nf; + + if (!(inno_read(inno, 0xa2) & RK3328_PRE_PLL_FRAC_DIV_DISABLE)) { + frac = inno_read(inno, 0xd3) | + (inno_read(inno, 0xd2) << 8) | + (inno_read(inno, 0xd1) << 16); + vco += DIV_ROUND_CLOSEST(parent_rate * frac, (1 << 24)); + } + + if (inno_read(inno, 0xa0) & RK3328_PCLK_VCO_DIV_5_MASK) { + do_div(vco, nd * 5); + } else { + no_a = inno_read(inno, 0xa5) & RK3328_PRE_PLL_PCLK_DIV_A_MASK; + no_b = inno_read(inno, 0xa5) & RK3328_PRE_PLL_PCLK_DIV_B_MASK; + no_b >>= RK3328_PRE_PLL_PCLK_DIV_B_SHIFT; + no_b += 2; + no_c = inno_read(inno, 0xa6) & RK3328_PRE_PLL_PCLK_DIV_C_MASK; + no_c >>= RK3328_PRE_PLL_PCLK_DIV_C_SHIFT; + no_c = 1 << no_c; + no_d = inno_read(inno, 0xa6) & RK3328_PRE_PLL_PCLK_DIV_D_MASK; + + do_div(vco, (nd * (no_a == 1 ? no_b : no_a) * no_d * 2)); + } + + inno->pixclock = vco; + dev_dbg(inno->dev, "%s rate %lu\n", __func__, inno->pixclock); + + return vco; +} + +static long inno_hdmi_phy_rk3328_clk_round_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long *parent_rate) +{ + const struct pre_pll_config *cfg = pre_pll_cfg_table; + + for (; cfg->pixclock != 0; cfg++) + if (cfg->pixclock == rate) + break; + + if (cfg->pixclock == 0) + return -EINVAL; + + return cfg->pixclock; +} + +static int inno_hdmi_phy_rk3328_clk_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct inno_hdmi_phy *inno = to_inno_hdmi_phy(hw); + const struct pre_pll_config *cfg = pre_pll_cfg_table; + unsigned long tmdsclock = inno_hdmi_phy_get_tmdsclk(inno, rate); + u32 val; + int ret; + + dev_dbg(inno->dev, "%s rate %lu tmdsclk %lu\n", + __func__, rate, tmdsclock); + + cfg = inno_hdmi_phy_get_pre_pll_cfg(inno, rate); + if (IS_ERR(cfg)) + return PTR_ERR(cfg); + + inno_update_bits(inno, 0xa0, RK3328_PRE_PLL_POWER_DOWN, + RK3328_PRE_PLL_POWER_DOWN); + + /* Configure pre-pll */ + inno_update_bits(inno, 0xa0, RK3228_PCLK_VCO_DIV_5_MASK, + RK3228_PCLK_VCO_DIV_5(cfg->vco_div_5_en)); + inno_write(inno, 0xa1, RK3328_PRE_PLL_PRE_DIV(cfg->prediv)); + + val = RK3328_SPREAD_SPECTRUM_MOD_DISABLE; + if (!cfg->fracdiv) + val |= RK3328_PRE_PLL_FRAC_DIV_DISABLE; + inno_write(inno, 0xa2, RK3328_PRE_PLL_FB_DIV_11_8(cfg->fbdiv) | val); + inno_write(inno, 0xa3, RK3328_PRE_PLL_FB_DIV_7_0(cfg->fbdiv)); + inno_write(inno, 0xa5, RK3328_PRE_PLL_PCLK_DIV_A(cfg->pclk_div_a) | + RK3328_PRE_PLL_PCLK_DIV_B(cfg->pclk_div_b)); + inno_write(inno, 0xa6, RK3328_PRE_PLL_PCLK_DIV_C(cfg->pclk_div_c) | + RK3328_PRE_PLL_PCLK_DIV_D(cfg->pclk_div_d)); + inno_write(inno, 0xa4, RK3328_PRE_PLL_TMDSCLK_DIV_C(cfg->tmds_div_c) | + RK3328_PRE_PLL_TMDSCLK_DIV_A(cfg->tmds_div_a) | + RK3328_PRE_PLL_TMDSCLK_DIV_B(cfg->tmds_div_b)); + inno_write(inno, 0xd3, RK3328_PRE_PLL_FRAC_DIV_7_0(cfg->fracdiv)); + inno_write(inno, 0xd2, RK3328_PRE_PLL_FRAC_DIV_15_8(cfg->fracdiv)); + inno_write(inno, 0xd1, RK3328_PRE_PLL_FRAC_DIV_23_16(cfg->fracdiv)); + + inno_update_bits(inno, 0xa0, RK3328_PRE_PLL_POWER_DOWN, 0); + + /* Wait for Pre-PLL lock */ + ret = inno_poll(inno, 0xa9, val, val & RK3328_PRE_PLL_LOCK_STATUS, + 1000, 10000); + if (ret) { + dev_err(inno->dev, "Pre-PLL locking failed\n"); + return ret; + } + + inno->pixclock = rate; + + return 0; +} + +static const struct clk_ops inno_hdmi_phy_rk3328_clk_ops = { + .prepare = inno_hdmi_phy_rk3328_clk_prepare, + .unprepare = inno_hdmi_phy_rk3328_clk_unprepare, + .is_prepared = inno_hdmi_phy_rk3328_clk_is_prepared, + .recalc_rate = inno_hdmi_phy_rk3328_clk_recalc_rate, + .round_rate = inno_hdmi_phy_rk3328_clk_round_rate, + .set_rate = inno_hdmi_phy_rk3328_clk_set_rate, +}; + +static int inno_hdmi_phy_clk_register(struct inno_hdmi_phy *inno) +{ + struct device *dev = inno->dev; + struct device_node *np = dev->of_node; + struct clk_init_data init; + const char *parent_name; + int ret; + + parent_name = __clk_get_name(inno->refoclk); + + init.parent_names = &parent_name; + init.num_parents = 1; + init.flags = 0; + init.name = "pin_hd20_pclk"; + init.ops = inno->plat_data->clk_ops; + + /* optional override of the clock name */ + of_property_read_string(np, "clock-output-names", &init.name); + + inno->hw.init = &init; + + inno->phyclk = devm_clk_register(dev, &inno->hw); + if (IS_ERR(inno->phyclk)) { + ret = PTR_ERR(inno->phyclk); + dev_err(dev, "failed to register clock: %d\n", ret); + return ret; + } + + ret = of_clk_add_provider(np, of_clk_src_simple_get, inno->phyclk); + if (ret) { + dev_err(dev, "failed to register clock provider: %d\n", ret); + return ret; + } + + return 0; +} + +static int inno_hdmi_phy_rk3228_init(struct inno_hdmi_phy *inno) +{ + /* + * Use phy internal register control + * rxsense/poweron/pllpd/pdataen signal. + */ + inno_write(inno, 0x01, RK3228_BYPASS_RXSENSE_EN | + RK3228_BYPASS_PWRON_EN | + RK3228_BYPASS_PLLPD_EN); + inno_update_bits(inno, 0x02, RK3228_BYPASS_PDATA_EN, + RK3228_BYPASS_PDATA_EN); + + /* manual power down post-PLL */ + inno_update_bits(inno, 0xaa, RK3228_POST_PLL_CTRL_MANUAL, + RK3228_POST_PLL_CTRL_MANUAL); + + inno->chip_version = 1; + + return 0; +} + +static int +inno_hdmi_phy_rk3228_power_on(struct inno_hdmi_phy *inno, + const struct post_pll_config *cfg, + const struct phy_config *phy_cfg) +{ + int ret; + u32 v; + + inno_update_bits(inno, 0x02, RK3228_PDATAEN_DISABLE, + RK3228_PDATAEN_DISABLE); + inno_update_bits(inno, 0xe0, RK3228_PRE_PLL_POWER_DOWN | + RK3228_POST_PLL_POWER_DOWN, + RK3228_PRE_PLL_POWER_DOWN | + RK3228_POST_PLL_POWER_DOWN); + + /* Post-PLL update */ + inno_update_bits(inno, 0xe9, RK3228_POST_PLL_PRE_DIV_MASK, + RK3228_POST_PLL_PRE_DIV(cfg->prediv)); + inno_update_bits(inno, 0xeb, RK3228_POST_PLL_FB_DIV_8_MASK, + RK3228_POST_PLL_FB_DIV_8(cfg->fbdiv)); + inno_write(inno, 0xea, RK3228_POST_PLL_FB_DIV_7_0(cfg->fbdiv)); + + if (cfg->postdiv == 1) { + inno_update_bits(inno, 0xe9, RK3228_POST_PLL_POST_DIV_ENABLE, + 0); + } else { + int div = cfg->postdiv / 2 - 1; + + inno_update_bits(inno, 0xe9, RK3228_POST_PLL_POST_DIV_ENABLE, + RK3228_POST_PLL_POST_DIV_ENABLE); + inno_update_bits(inno, 0xeb, RK3228_POST_PLL_POST_DIV_MASK, + RK3228_POST_PLL_POST_DIV(div)); + } + + for (v = 0; v < 4; v++) + inno_write(inno, 0xef + v, phy_cfg->regs[v]); + + inno_update_bits(inno, 0xe0, RK3228_PRE_PLL_POWER_DOWN | + RK3228_POST_PLL_POWER_DOWN, 0); + inno_update_bits(inno, 0xe1, RK3228_BANDGAP_ENABLE, + RK3228_BANDGAP_ENABLE); + inno_update_bits(inno, 0xe1, RK3228_TMDS_DRIVER_ENABLE, + RK3228_TMDS_DRIVER_ENABLE); + + /* Wait for post PLL lock */ + ret = inno_poll(inno, 0xeb, v, v & RK3228_POST_PLL_LOCK_STATUS, + 100, 100000); + if (ret) { + dev_err(inno->dev, "Post-PLL locking failed\n"); + return ret; + } + + if (cfg->tmdsclock > 340000000) + msleep(100); + + inno_update_bits(inno, 0x02, RK3228_PDATAEN_DISABLE, 0); + return 0; +} + +static void inno_hdmi_phy_rk3228_power_off(struct inno_hdmi_phy *inno) +{ + inno_update_bits(inno, 0xe1, RK3228_TMDS_DRIVER_ENABLE, 0); + inno_update_bits(inno, 0xe1, RK3228_BANDGAP_ENABLE, 0); + inno_update_bits(inno, 0xe0, RK3228_POST_PLL_POWER_DOWN, + RK3228_POST_PLL_POWER_DOWN); +} + +static const struct inno_hdmi_phy_ops rk3228_hdmi_phy_ops = { + .init = inno_hdmi_phy_rk3228_init, + .power_on = inno_hdmi_phy_rk3228_power_on, + .power_off = inno_hdmi_phy_rk3228_power_off, +}; + +static int inno_hdmi_phy_rk3328_init(struct inno_hdmi_phy *inno) +{ + struct nvmem_cell *cell; + unsigned char *efuse_buf; + size_t len; + + /* + * Use phy internal register control + * rxsense/poweron/pllpd/pdataen signal. + */ + inno_write(inno, 0x01, RK3328_BYPASS_RXSENSE_EN | + RK3328_BYPASS_POWERON_EN | + RK3328_BYPASS_PLLPD_EN); + inno_write(inno, 0x02, RK3328_INT_POL_HIGH | RK3328_BYPASS_PDATA_EN | + RK3328_PDATA_EN); + + /* Disable phy irq */ + inno_write(inno, 0x05, 0); + inno_write(inno, 0x07, 0); + + /* try to read the chip-version */ + inno->chip_version = 1; + cell = nvmem_cell_get(inno->dev, "cpu-version"); + if (IS_ERR(cell)) { + if (PTR_ERR(cell) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + return 0; + } + + efuse_buf = nvmem_cell_read(cell, &len); + nvmem_cell_put(cell); + + if (IS_ERR(efuse_buf)) + return 0; + if (len == 1) + inno->chip_version = efuse_buf[0] + 1; + kfree(efuse_buf); + + return 0; +} + +static int +inno_hdmi_phy_rk3328_power_on(struct inno_hdmi_phy *inno, + const struct post_pll_config *cfg, + const struct phy_config *phy_cfg) +{ + int ret; + u32 v; + + inno_update_bits(inno, 0x02, RK3328_PDATA_EN, 0); + inno_update_bits(inno, 0xaa, RK3328_POST_PLL_POWER_DOWN, + RK3328_POST_PLL_POWER_DOWN); + + inno_write(inno, 0xac, RK3328_POST_PLL_FB_DIV_7_0(cfg->fbdiv)); + if (cfg->postdiv == 1) { + inno_write(inno, 0xaa, RK3328_POST_PLL_REFCLK_SEL_TMDS); + inno_write(inno, 0xab, RK3328_POST_PLL_FB_DIV_8(cfg->fbdiv) | + RK3328_POST_PLL_PRE_DIV(cfg->prediv)); + } else { + v = (cfg->postdiv / 2) - 1; + v &= RK3328_POST_PLL_POST_DIV_MASK; + inno_write(inno, 0xad, v); + inno_write(inno, 0xab, RK3328_POST_PLL_FB_DIV_8(cfg->fbdiv) | + RK3328_POST_PLL_PRE_DIV(cfg->prediv)); + inno_write(inno, 0xaa, RK3328_POST_PLL_POST_DIV_ENABLE | + RK3328_POST_PLL_REFCLK_SEL_TMDS); + } + + for (v = 0; v < 14; v++) + inno_write(inno, 0xb5 + v, phy_cfg->regs[v]); + + /* set ESD detection threshold for TMDS CLK, D2, D1 and D0 */ + for (v = 0; v < 4; v++) + inno_update_bits(inno, 0xc8 + v, RK3328_ESD_DETECT_MASK, + RK3328_ESD_DETECT_340MV); + + if (phy_cfg->tmdsclock > 340000000) { + /* Set termination resistor to 100ohm */ + v = clk_get_rate(inno->sysclk) / 100000; + inno_write(inno, 0xc5, RK3328_TERM_RESISTOR_CALIB_SPEED_14_8(v) + | RK3328_BYPASS_TERM_RESISTOR_CALIB); + inno_write(inno, 0xc6, RK3328_TERM_RESISTOR_CALIB_SPEED_7_0(v)); + inno_write(inno, 0xc7, RK3328_TERM_RESISTOR_100); + inno_update_bits(inno, 0xc5, + RK3328_BYPASS_TERM_RESISTOR_CALIB, 0); + } else { + inno_write(inno, 0xc5, RK3328_BYPASS_TERM_RESISTOR_CALIB); + + /* clk termination resistor is 50ohm (parallel resistors) */ + if (phy_cfg->tmdsclock > 165000000) + inno_update_bits(inno, 0xc8, + RK3328_TMDS_TERM_RESIST_MASK, + RK3328_TMDS_TERM_RESIST_75 | + RK3328_TMDS_TERM_RESIST_150); + + /* data termination resistor for D2, D1 and D0 is 150ohm */ + for (v = 0; v < 3; v++) + inno_update_bits(inno, 0xc9 + v, + RK3328_TMDS_TERM_RESIST_MASK, + RK3328_TMDS_TERM_RESIST_150); + } + + inno_update_bits(inno, 0xaa, RK3328_POST_PLL_POWER_DOWN, 0); + inno_update_bits(inno, 0xb0, RK3328_BANDGAP_ENABLE, + RK3328_BANDGAP_ENABLE); + inno_update_bits(inno, 0xb2, RK3328_TMDS_DRIVER_ENABLE, + RK3328_TMDS_DRIVER_ENABLE); + + /* Wait for post PLL lock */ + ret = inno_poll(inno, 0xaf, v, v & RK3328_POST_PLL_LOCK_STATUS, + 1000, 10000); + if (ret) { + dev_err(inno->dev, "Post-PLL locking failed\n"); + return ret; + } + + if (phy_cfg->tmdsclock > 340000000) + msleep(100); + + inno_update_bits(inno, 0x02, RK3328_PDATA_EN, RK3328_PDATA_EN); + + /* Enable PHY IRQ */ + inno_write(inno, 0x05, RK3328_INT_TMDS_CLK(RK3328_INT_VSS_AGND_ESD_DET) + | RK3328_INT_TMDS_D2(RK3328_INT_VSS_AGND_ESD_DET)); + inno_write(inno, 0x07, RK3328_INT_TMDS_D1(RK3328_INT_VSS_AGND_ESD_DET) + | RK3328_INT_TMDS_D0(RK3328_INT_VSS_AGND_ESD_DET)); + return 0; +} + +static void inno_hdmi_phy_rk3328_power_off(struct inno_hdmi_phy *inno) +{ + inno_update_bits(inno, 0xb2, RK3328_TMDS_DRIVER_ENABLE, 0); + inno_update_bits(inno, 0xb0, RK3328_BANDGAP_ENABLE, 0); + inno_update_bits(inno, 0xaa, RK3328_POST_PLL_POWER_DOWN, + RK3328_POST_PLL_POWER_DOWN); + + /* Disable PHY IRQ */ + inno_write(inno, 0x05, 0); + inno_write(inno, 0x07, 0); +} + +static const struct inno_hdmi_phy_ops rk3328_hdmi_phy_ops = { + .init = inno_hdmi_phy_rk3328_init, + .power_on = inno_hdmi_phy_rk3328_power_on, + .power_off = inno_hdmi_phy_rk3328_power_off, +}; + +static const struct inno_hdmi_phy_drv_data rk3228_hdmi_phy_drv_data = { + .ops = &rk3228_hdmi_phy_ops, + .clk_ops = &inno_hdmi_phy_rk3228_clk_ops, + .phy_cfg_table = rk3228_phy_cfg, +}; + +static const struct inno_hdmi_phy_drv_data rk3328_hdmi_phy_drv_data = { + .ops = &rk3328_hdmi_phy_ops, + .clk_ops = &inno_hdmi_phy_rk3328_clk_ops, + .phy_cfg_table = rk3328_phy_cfg, +}; + +static const struct regmap_config inno_hdmi_phy_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x400, +}; + +static void inno_hdmi_phy_action(void *data) +{ + struct inno_hdmi_phy *inno = data; + + clk_disable_unprepare(inno->refpclk); + clk_disable_unprepare(inno->sysclk); +} + +static int inno_hdmi_phy_probe(struct platform_device *pdev) +{ + struct inno_hdmi_phy *inno; + struct phy_provider *phy_provider; + struct resource *res; + void __iomem *regs; + int ret; + + inno = devm_kzalloc(&pdev->dev, sizeof(*inno), GFP_KERNEL); + if (!inno) + return -ENOMEM; + + inno->dev = &pdev->dev; + + inno->plat_data = of_device_get_match_data(inno->dev); + if (!inno->plat_data || !inno->plat_data->ops) + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(inno->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + inno->sysclk = devm_clk_get(inno->dev, "sysclk"); + if (IS_ERR(inno->sysclk)) { + ret = PTR_ERR(inno->sysclk); + dev_err(inno->dev, "failed to get sysclk: %d\n", ret); + return ret; + } + + inno->refpclk = devm_clk_get(inno->dev, "refpclk"); + if (IS_ERR(inno->refpclk)) { + ret = PTR_ERR(inno->refpclk); + dev_err(inno->dev, "failed to get ref clock: %d\n", ret); + return ret; + } + + inno->refoclk = devm_clk_get(inno->dev, "refoclk"); + if (IS_ERR(inno->refoclk)) { + ret = PTR_ERR(inno->refoclk); + dev_err(inno->dev, "failed to get oscillator-ref clock: %d\n", + ret); + return ret; + } + + ret = clk_prepare_enable(inno->sysclk); + if (ret) { + dev_err(inno->dev, "Cannot enable inno phy sysclk: %d\n", ret); + return ret; + } + + /* + * Refpclk needs to be on, on at least the rk3328 for still + * unknown reasons. + */ + ret = clk_prepare_enable(inno->refpclk); + if (ret) { + dev_err(inno->dev, "failed to enable refpclk\n"); + clk_disable_unprepare(inno->sysclk); + return ret; + } + + ret = devm_add_action_or_reset(inno->dev, inno_hdmi_phy_action, + inno); + if (ret) + return ret; + + inno->regmap = devm_regmap_init_mmio(inno->dev, regs, + &inno_hdmi_phy_regmap_config); + if (IS_ERR(inno->regmap)) + return PTR_ERR(inno->regmap); + + /* only the newer rk3328 hdmiphy has an interrupt */ + inno->irq = platform_get_irq(pdev, 0); + if (inno->irq > 0) { + ret = devm_request_threaded_irq(inno->dev, inno->irq, + inno_hdmi_phy_rk3328_hardirq, + inno_hdmi_phy_rk3328_irq, + IRQF_SHARED, + dev_name(inno->dev), inno); + if (ret) + return ret; + } + + inno->phy = devm_phy_create(inno->dev, NULL, &inno_hdmi_phy_ops); + if (IS_ERR(inno->phy)) { + dev_err(inno->dev, "failed to create HDMI PHY\n"); + return PTR_ERR(inno->phy); + } + + phy_set_drvdata(inno->phy, inno); + phy_set_bus_width(inno->phy, 8); + + if (inno->plat_data->ops->init) { + ret = inno->plat_data->ops->init(inno); + if (ret) + return ret; + } + + ret = inno_hdmi_phy_clk_register(inno); + if (ret) + return ret; + + phy_provider = devm_of_phy_provider_register(inno->dev, + of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static int inno_hdmi_phy_remove(struct platform_device *pdev) +{ + of_clk_del_provider(pdev->dev.of_node); + + return 0; +} + +static const struct of_device_id inno_hdmi_phy_of_match[] = { + { + .compatible = "rockchip,rk3228-hdmi-phy", + .data = &rk3228_hdmi_phy_drv_data + }, { + .compatible = "rockchip,rk3328-hdmi-phy", + .data = &rk3328_hdmi_phy_drv_data + }, { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, inno_hdmi_phy_of_match); + +static struct platform_driver inno_hdmi_phy_driver = { + .probe = inno_hdmi_phy_probe, + .remove = inno_hdmi_phy_remove, + .driver = { + .name = "inno-hdmi-phy", + .of_match_table = inno_hdmi_phy_of_match, + }, +}; +module_platform_driver(inno_hdmi_phy_driver); + +MODULE_AUTHOR("Zheng Yang "); +MODULE_DESCRIPTION("Innosilion HDMI 2.0 Transmitter PHY Driver"); +MODULE_LICENSE("GPL v2"); From ad05573080997aab7b5d0ea6187d88ef996ae3e5 Mon Sep 17 00:00:00 2001 From: Fabrizio Castro Date: Fri, 24 Aug 2018 08:56:10 +0100 Subject: [PATCH 15/41] dt-bindings: rcar-gen3-phy-usb2: Add r8a774a1 support Document RZ/G2M (R8A774A1) SoC bindings. Signed-off-by: Fabrizio Castro Reviewed-by: Biju Das Reviewed-by: Yoshihiro Shimoda Reviewed-by: Simon Horman Reviewed-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- .../devicetree/bindings/phy/rcar-gen3-phy-usb2.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt index fb4a204da2bf1..6a4b5b626b41a 100644 --- a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt +++ b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt @@ -1,10 +1,12 @@ * Renesas R-Car generation 3 USB 2.0 PHY This file provides information on what the device node for the R-Car generation -3 USB 2.0 PHY contains. +3 and RZ/G2 USB 2.0 PHY contain. Required properties: -- compatible: "renesas,usb2-phy-r8a7795" if the device is a part of an R8A7795 +- compatible: "renesas,usb2-phy-r8a774a1" if the device is a part of an R8A774A1 + SoC. + "renesas,usb2-phy-r8a7795" if the device is a part of an R8A7795 SoC. "renesas,usb2-phy-r8a7796" if the device is a part of an R8A7796 SoC. @@ -14,7 +16,8 @@ Required properties: R8A77990 SoC. "renesas,usb2-phy-r8a77995" if the device is a part of an R8A77995 SoC. - "renesas,rcar-gen3-usb2-phy" for a generic R-Car Gen3 compatible device. + "renesas,rcar-gen3-usb2-phy" for a generic R-Car Gen3 or RZ/G2 + compatible device. When compatible with the generic version, nodes must list the SoC-specific version corresponding to the platform first From fcd0eec4f54f1a8627a2d7fcbaaeed18261276a4 Mon Sep 17 00:00:00 2001 From: Fabrizio Castro Date: Fri, 24 Aug 2018 08:56:11 +0100 Subject: [PATCH 16/41] dt-bindings: rcar-gen3-phy-usb3: Add r8a774a1 support Document RZ/G2M (R8A774A1) SoC bindings. Signed-off-by: Fabrizio Castro Reviewed-by: Biju Das Reviewed-by: Yoshihiro Shimoda Reviewed-by: Simon Horman Reviewed-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- .../devicetree/bindings/phy/rcar-gen3-phy-usb3.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb3.txt b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb3.txt index 47dd296ecead9..9d9826609c2f9 100644 --- a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb3.txt +++ b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb3.txt @@ -1,20 +1,22 @@ * Renesas R-Car generation 3 USB 3.0 PHY This file provides information on what the device node for the R-Car generation -3 USB 3.0 PHY contains. +3 and RZ/G2 USB 3.0 PHY contain. If you want to enable spread spectrum clock (ssc), you should use USB_EXTAL instead of USB3_CLK. However, if you don't want to these features, you don't need this driver. Required properties: -- compatible: "renesas,r8a7795-usb3-phy" if the device is a part of an R8A7795 +- compatible: "renesas,r8a774a1-usb3-phy" if the device is a part of an R8A774A1 + SoC. + "renesas,r8a7795-usb3-phy" if the device is a part of an R8A7795 SoC. "renesas,r8a7796-usb3-phy" if the device is a part of an R8A7796 SoC. "renesas,r8a77965-usb3-phy" if the device is a part of an R8A77965 SoC. - "renesas,rcar-gen3-usb3-phy" for a generic R-Car Gen3 compatible - device. + "renesas,rcar-gen3-usb3-phy" for a generic R-Car Gen3 or RZ/G2 + compatible device. When compatible with the generic version, nodes must list the SoC-specific version corresponding to the platform first From ac9ba7dc8613773b037a96af24f381ef230ef1ae Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Mon, 27 Aug 2018 20:52:40 -0500 Subject: [PATCH 17/41] phy: Convert to using %pOFn instead of device_node.name In preparation to remove the node name pointer from struct device_node, convert printf users to use the %pOFn format specifier. Cc: Kishon Vijay Abraham I Cc: linux-arm-kernel@lists.infradead.org Signed-off-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/broadcom/phy-bcm-cygnus-pcie.c | 4 ++-- drivers/phy/broadcom/phy-brcm-sata.c | 4 ++-- drivers/phy/marvell/phy-berlin-sata.c | 6 +++--- drivers/phy/qualcomm/phy-qcom-qmp.c | 2 +- drivers/phy/rockchip/phy-rockchip-emmc.c | 4 ++-- drivers/phy/rockchip/phy-rockchip-inno-usb2.c | 8 ++++---- drivers/phy/rockchip/phy-rockchip-typec.c | 8 ++++---- drivers/phy/rockchip/phy-rockchip-usb.c | 4 ++-- drivers/phy/tegra/xusb.c | 4 ++-- 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/drivers/phy/broadcom/phy-bcm-cygnus-pcie.c b/drivers/phy/broadcom/phy-bcm-cygnus-pcie.c index 0f4ac5d63cff9..b074682d9dd88 100644 --- a/drivers/phy/broadcom/phy-bcm-cygnus-pcie.c +++ b/drivers/phy/broadcom/phy-bcm-cygnus-pcie.c @@ -153,8 +153,8 @@ static int cygnus_pcie_phy_probe(struct platform_device *pdev) struct cygnus_pcie_phy *p; if (of_property_read_u32(child, "reg", &id)) { - dev_err(dev, "missing reg property for %s\n", - child->name); + dev_err(dev, "missing reg property for %pOFn\n", + child); ret = -EINVAL; goto put_child; } diff --git a/drivers/phy/broadcom/phy-brcm-sata.c b/drivers/phy/broadcom/phy-brcm-sata.c index 8708ea3b4d6d4..ac57f5a41708b 100644 --- a/drivers/phy/broadcom/phy-brcm-sata.c +++ b/drivers/phy/broadcom/phy-brcm-sata.c @@ -600,8 +600,8 @@ static int brcm_sata_phy_probe(struct platform_device *pdev) struct brcm_sata_port *port; if (of_property_read_u32(child, "reg", &id)) { - dev_err(dev, "missing reg property in node %s\n", - child->name); + dev_err(dev, "missing reg property in node %pOFn\n", + child); ret = -EINVAL; goto put_child; } diff --git a/drivers/phy/marvell/phy-berlin-sata.c b/drivers/phy/marvell/phy-berlin-sata.c index c1bb6725e48f1..a91fc67fc4e0c 100644 --- a/drivers/phy/marvell/phy-berlin-sata.c +++ b/drivers/phy/marvell/phy-berlin-sata.c @@ -231,14 +231,14 @@ static int phy_berlin_sata_probe(struct platform_device *pdev) struct phy_berlin_desc *phy_desc; if (of_property_read_u32(child, "reg", &phy_id)) { - dev_err(dev, "missing reg property in node %s\n", - child->name); + dev_err(dev, "missing reg property in node %pOFn\n", + child); ret = -EINVAL; goto put_child; } if (phy_id >= ARRAY_SIZE(phy_berlin_power_down_bits)) { - dev_err(dev, "invalid reg in node %s\n", child->name); + dev_err(dev, "invalid reg in node %pOFn\n", child); ret = -EINVAL; goto put_child; } diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.c b/drivers/phy/qualcomm/phy-qcom-qmp.c index 72efc2e14ab6e..7932931a3da30 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp.c @@ -1400,7 +1400,7 @@ static int phy_pipe_clk_register(struct qcom_qmp *qmp, struct device_node *np) ret = of_property_read_string(np, "clock-output-names", &init.name); if (ret) { - dev_err(qmp->dev, "%s: No clock-output-names\n", np->name); + dev_err(qmp->dev, "%pOFn: No clock-output-names\n", np); return ret; } diff --git a/drivers/phy/rockchip/phy-rockchip-emmc.c b/drivers/phy/rockchip/phy-rockchip-emmc.c index b237360f95f61..19bf84f0bc672 100644 --- a/drivers/phy/rockchip/phy-rockchip-emmc.c +++ b/drivers/phy/rockchip/phy-rockchip-emmc.c @@ -337,8 +337,8 @@ static int rockchip_emmc_phy_probe(struct platform_device *pdev) return -ENOMEM; if (of_property_read_u32(dev->of_node, "reg", ®_offset)) { - dev_err(dev, "missing reg property in node %s\n", - dev->of_node->name); + dev_err(dev, "missing reg property in node %pOFn\n", + dev->of_node); return -EINVAL; } diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c index 5049dac79bd0f..24bd2717abdb9 100644 --- a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c @@ -1116,8 +1116,8 @@ static int rockchip_usb2phy_probe(struct platform_device *pdev) } if (of_property_read_u32(np, "reg", ®)) { - dev_err(dev, "the reg property is not assigned in %s node\n", - np->name); + dev_err(dev, "the reg property is not assigned in %pOFn node\n", + np); return -EINVAL; } @@ -1143,8 +1143,8 @@ static int rockchip_usb2phy_probe(struct platform_device *pdev) } if (!rphy->phy_cfg) { - dev_err(dev, "no phy-config can be matched with %s node\n", - np->name); + dev_err(dev, "no phy-config can be matched with %pOFn node\n", + np); return -EINVAL; } diff --git a/drivers/phy/rockchip/phy-rockchip-typec.c b/drivers/phy/rockchip/phy-rockchip-typec.c index 76a4b58ec7717..c57e496f0b0c5 100644 --- a/drivers/phy/rockchip/phy-rockchip-typec.c +++ b/drivers/phy/rockchip/phy-rockchip-typec.c @@ -1145,8 +1145,8 @@ static int rockchip_typec_phy_probe(struct platform_device *pdev) } if (!tcphy->port_cfgs) { - dev_err(dev, "no phy-config can be matched with %s node\n", - np->name); + dev_err(dev, "no phy-config can be matched with %pOFn node\n", + np); return -EINVAL; } @@ -1186,8 +1186,8 @@ static int rockchip_typec_phy_probe(struct platform_device *pdev) continue; if (IS_ERR(phy)) { - dev_err(dev, "failed to create phy: %s\n", - child_np->name); + dev_err(dev, "failed to create phy: %pOFn\n", + child_np); pm_runtime_disable(dev); return PTR_ERR(phy); } diff --git a/drivers/phy/rockchip/phy-rockchip-usb.c b/drivers/phy/rockchip/phy-rockchip-usb.c index 3378eeb7a562f..269c86329cb86 100644 --- a/drivers/phy/rockchip/phy-rockchip-usb.c +++ b/drivers/phy/rockchip/phy-rockchip-usb.c @@ -208,8 +208,8 @@ static int rockchip_usb_phy_init(struct rockchip_usb_phy_base *base, rk_phy->np = child; if (of_property_read_u32(child, "reg", ®_offset)) { - dev_err(base->dev, "missing reg property in node %s\n", - child->name); + dev_err(base->dev, "missing reg property in node %pOFn\n", + child); return -EINVAL; } diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index de1b4ebe4de28..5b3b8863363ed 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -115,8 +115,8 @@ int tegra_xusb_lane_parse_dt(struct tegra_xusb_lane *lane, err = match_string(lane->soc->funcs, lane->soc->num_funcs, function); if (err < 0) { - dev_err(dev, "invalid function \"%s\" for lane \"%s\"\n", - function, np->name); + dev_err(dev, "invalid function \"%s\" for lane \"%pOFn\"\n", + function, np); return err; } From 74c60cd96b5c8b63be69881fa7da514eae240744 Mon Sep 17 00:00:00 2001 From: Kunihiko Hayashi Date: Wed, 5 Sep 2018 18:49:44 +0900 Subject: [PATCH 18/41] dt-bindings: phy: add UniPhier PCIe PHY description Add DT bindings for PHY interface built into PCIe controller implemented in UniPhier SoCs. Signed-off-by: Kunihiko Hayashi Reviewed-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- .../bindings/phy/uniphier-pcie-phy.txt | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/uniphier-pcie-phy.txt diff --git a/Documentation/devicetree/bindings/phy/uniphier-pcie-phy.txt b/Documentation/devicetree/bindings/phy/uniphier-pcie-phy.txt new file mode 100644 index 0000000000000..1889d3b89d68f --- /dev/null +++ b/Documentation/devicetree/bindings/phy/uniphier-pcie-phy.txt @@ -0,0 +1,31 @@ +Socionext UniPhier PCIe PHY bindings + +This describes the devicetree bindings for PHY interface built into +PCIe controller implemented on Socionext UniPhier SoCs. + +Required properties: +- compatible: Should contain one of the following: + "socionext,uniphier-ld20-pcie-phy" - for LD20 PHY + "socionext,uniphier-pxs3-pcie-phy" - for PXs3 PHY +- reg: Specifies offset and length of the register set for the device. +- #phy-cells: Must be zero. +- clocks: A phandle to the clock gate for PCIe glue layer including + this phy. +- resets: A phandle to the reset line for PCIe glue layer including + this phy. + +Optional properties: +- socionext,syscon: A phandle to system control to set configurations + for phy. + +Refer to phy/phy-bindings.txt for the generic PHY binding properties. + +Example: + pcie_phy: phy@66038000 { + compatible = "socionext,uniphier-ld20-pcie-phy"; + reg = <0x66038000 0x4000>; + #phy-cells = <0>; + clocks = <&sys_clk 24>; + resets = <&sys_rst 24>; + socionext,syscon = <&soc_glue>; + }; From c6d9b132415951a8e8025a5b0e7f6b805737528c Mon Sep 17 00:00:00 2001 From: Kunihiko Hayashi Date: Wed, 5 Sep 2018 18:49:45 +0900 Subject: [PATCH 19/41] phy: socionext: add PCIe PHY driver support Add a driver for PHY interface built into PCIe controller implemented in UniPhier SoCs. Signed-off-by: Kunihiko Hayashi Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/socionext/Kconfig | 9 + drivers/phy/socionext/Makefile | 1 + drivers/phy/socionext/phy-uniphier-pcie.c | 240 ++++++++++++++++++++++ 3 files changed, 250 insertions(+) create mode 100644 drivers/phy/socionext/phy-uniphier-pcie.c diff --git a/drivers/phy/socionext/Kconfig b/drivers/phy/socionext/Kconfig index 497ca38214521..467e8147972b0 100644 --- a/drivers/phy/socionext/Kconfig +++ b/drivers/phy/socionext/Kconfig @@ -23,3 +23,12 @@ config PHY_UNIPHIER_USB3 help Enable this to support USB PHY implemented in USB3 controller on UniPhier SoCs. This controller supports USB3.0 and lower speed. + +config PHY_UNIPHIER_PCIE + tristate "Uniphier PHY driver for PCIe controller" + depends on (ARCH_UNIPHIER || COMPILE_TEST) && OF + default PCIE_UNIPHIER + select GENERIC_PHY + help + Enable this to support PHY implemented in PCIe controller + on UniPhier SoCs. This driver supports LD20 and PXs3 SoCs. diff --git a/drivers/phy/socionext/Makefile b/drivers/phy/socionext/Makefile index 91e482564386d..7dc9095b5bb7d 100644 --- a/drivers/phy/socionext/Makefile +++ b/drivers/phy/socionext/Makefile @@ -5,3 +5,4 @@ obj-$(CONFIG_PHY_UNIPHIER_USB2) += phy-uniphier-usb2.o obj-$(CONFIG_PHY_UNIPHIER_USB3) += phy-uniphier-usb3hs.o phy-uniphier-usb3ss.o +obj-$(CONFIG_PHY_UNIPHIER_PCIE) += phy-uniphier-pcie.o diff --git a/drivers/phy/socionext/phy-uniphier-pcie.c b/drivers/phy/socionext/phy-uniphier-pcie.c new file mode 100644 index 0000000000000..93ffbd2940fa4 --- /dev/null +++ b/drivers/phy/socionext/phy-uniphier-pcie.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * phy-uniphier-pcie.c - PHY driver for UniPhier PCIe controller + * Copyright 2018, Socionext Inc. + * Author: Kunihiko Hayashi + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* PHY */ +#define PCL_PHY_TEST_I 0x2000 +#define PCL_PHY_TEST_O 0x2004 +#define TESTI_DAT_MASK GENMASK(13, 6) +#define TESTI_ADR_MASK GENMASK(5, 1) +#define TESTI_WR_EN BIT(0) + +#define PCL_PHY_RESET 0x200c +#define PCL_PHY_RESET_N_MNMODE BIT(8) /* =1:manual */ +#define PCL_PHY_RESET_N BIT(0) /* =1:deasssert */ + +/* SG */ +#define SG_USBPCIESEL 0x590 +#define SG_USBPCIESEL_PCIE BIT(0) + +#define PCL_PHY_R00 0 +#define RX_EQ_ADJ_EN BIT(3) /* enable for EQ adjustment */ +#define PCL_PHY_R06 6 +#define RX_EQ_ADJ GENMASK(5, 0) /* EQ adjustment value */ +#define RX_EQ_ADJ_VAL 0 +#define PCL_PHY_R26 26 +#define VCO_CTRL GENMASK(7, 4) /* Tx VCO adjustment value */ +#define VCO_CTRL_INIT_VAL 5 + +struct uniphier_pciephy_priv { + void __iomem *base; + struct device *dev; + struct clk *clk; + struct reset_control *rst; + const struct uniphier_pciephy_soc_data *data; +}; + +struct uniphier_pciephy_soc_data { + bool has_syscon; +}; + +static void uniphier_pciephy_testio_write(struct uniphier_pciephy_priv *priv, + u32 data) +{ + /* need to read TESTO twice after accessing TESTI */ + writel(data, priv->base + PCL_PHY_TEST_I); + readl(priv->base + PCL_PHY_TEST_O); + readl(priv->base + PCL_PHY_TEST_O); +} + +static void uniphier_pciephy_set_param(struct uniphier_pciephy_priv *priv, + u32 reg, u32 mask, u32 param) +{ + u32 val; + + /* read previous data */ + val = FIELD_PREP(TESTI_DAT_MASK, 1); + val |= FIELD_PREP(TESTI_ADR_MASK, reg); + uniphier_pciephy_testio_write(priv, val); + val = readl(priv->base + PCL_PHY_TEST_O); + + /* update value */ + val &= ~FIELD_PREP(TESTI_DAT_MASK, mask); + val = FIELD_PREP(TESTI_DAT_MASK, mask & param); + val |= FIELD_PREP(TESTI_ADR_MASK, reg); + uniphier_pciephy_testio_write(priv, val); + uniphier_pciephy_testio_write(priv, val | TESTI_WR_EN); + uniphier_pciephy_testio_write(priv, val); + + /* read current data as dummy */ + val = FIELD_PREP(TESTI_DAT_MASK, 1); + val |= FIELD_PREP(TESTI_ADR_MASK, reg); + uniphier_pciephy_testio_write(priv, val); + readl(priv->base + PCL_PHY_TEST_O); +} + +static void uniphier_pciephy_assert(struct uniphier_pciephy_priv *priv) +{ + u32 val; + + val = readl(priv->base + PCL_PHY_RESET); + val &= ~PCL_PHY_RESET_N; + val |= PCL_PHY_RESET_N_MNMODE; + writel(val, priv->base + PCL_PHY_RESET); +} + +static void uniphier_pciephy_deassert(struct uniphier_pciephy_priv *priv) +{ + u32 val; + + val = readl(priv->base + PCL_PHY_RESET); + val |= PCL_PHY_RESET_N_MNMODE | PCL_PHY_RESET_N; + writel(val, priv->base + PCL_PHY_RESET); +} + +static int uniphier_pciephy_init(struct phy *phy) +{ + struct uniphier_pciephy_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = clk_prepare_enable(priv->clk); + if (ret) + return ret; + + ret = reset_control_deassert(priv->rst); + if (ret) + goto out_clk_disable; + + uniphier_pciephy_set_param(priv, PCL_PHY_R00, + RX_EQ_ADJ_EN, RX_EQ_ADJ_EN); + uniphier_pciephy_set_param(priv, PCL_PHY_R06, RX_EQ_ADJ, + FIELD_PREP(RX_EQ_ADJ, RX_EQ_ADJ_VAL)); + uniphier_pciephy_set_param(priv, PCL_PHY_R26, VCO_CTRL, + FIELD_PREP(VCO_CTRL, VCO_CTRL_INIT_VAL)); + usleep_range(1, 10); + + uniphier_pciephy_deassert(priv); + usleep_range(1, 10); + + return 0; + +out_clk_disable: + clk_disable_unprepare(priv->clk); + + return ret; +} + +static int uniphier_pciephy_exit(struct phy *phy) +{ + struct uniphier_pciephy_priv *priv = phy_get_drvdata(phy); + + uniphier_pciephy_assert(priv); + reset_control_assert(priv->rst); + clk_disable_unprepare(priv->clk); + + return 0; +} + +static const struct phy_ops uniphier_pciephy_ops = { + .init = uniphier_pciephy_init, + .exit = uniphier_pciephy_exit, + .owner = THIS_MODULE, +}; + +static int uniphier_pciephy_probe(struct platform_device *pdev) +{ + struct uniphier_pciephy_priv *priv; + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct regmap *regmap; + struct resource *res; + struct phy *phy; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->data = of_device_get_match_data(dev); + if (WARN_ON(!priv->data)) + return -EINVAL; + + priv->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + priv->rst = devm_reset_control_get_shared(dev, NULL); + if (IS_ERR(priv->rst)) + return PTR_ERR(priv->rst); + + phy = devm_phy_create(dev, dev->of_node, &uniphier_pciephy_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + regmap = syscon_regmap_lookup_by_phandle(dev->of_node, + "socionext,syscon"); + if (!IS_ERR(regmap) && priv->data->has_syscon) + regmap_update_bits(regmap, SG_USBPCIESEL, + SG_USBPCIESEL_PCIE, SG_USBPCIESEL_PCIE); + + phy_set_drvdata(phy, priv); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct uniphier_pciephy_soc_data uniphier_ld20_data = { + .has_syscon = true, +}; + +static const struct uniphier_pciephy_soc_data uniphier_pxs3_data = { + .has_syscon = false, +}; + +static const struct of_device_id uniphier_pciephy_match[] = { + { + .compatible = "socionext,uniphier-ld20-pcie-phy", + .data = &uniphier_ld20_data, + }, + { + .compatible = "socionext,uniphier-pxs3-pcie-phy", + .data = &uniphier_pxs3_data, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, uniphier_pciephy_match); + +static struct platform_driver uniphier_pciephy_driver = { + .probe = uniphier_pciephy_probe, + .driver = { + .name = "uniphier-pcie-phy", + .of_match_table = uniphier_pciephy_match, + }, +}; +module_platform_driver(uniphier_pciephy_driver); + +MODULE_AUTHOR("Kunihiko Hayashi "); +MODULE_DESCRIPTION("UniPhier PHY driver for PCIe controller"); +MODULE_LICENSE("GPL v2"); From a575388a9fbe8e70f3d86010698a57ae04a95fb4 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Thu, 20 Sep 2018 12:16:35 -0700 Subject: [PATCH 20/41] dt-bindings: phy: Document BCM63138 compatible string Document the compatible string "brcm,bcm63138-sata-phy" as a valid compatible string describing the standard Broadcom SATA PHY block. Signed-off-by: Florian Fainelli Signed-off-by: Kishon Vijay Abraham I --- Documentation/devicetree/bindings/phy/brcm-sata-phy.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/phy/brcm-sata-phy.txt b/Documentation/devicetree/bindings/phy/brcm-sata-phy.txt index 0aced97d8092a..b640845fec67c 100644 --- a/Documentation/devicetree/bindings/phy/brcm-sata-phy.txt +++ b/Documentation/devicetree/bindings/phy/brcm-sata-phy.txt @@ -8,6 +8,7 @@ Required properties: "brcm,iproc-nsp-sata-phy" "brcm,phy-sata3" "brcm,iproc-sr-sata-phy" + "brcm,bcm63138-sata-phy" - address-cells: should be 1 - size-cells: should be 0 - reg: register ranges for the PHY PCB interface From 26728df4b254ae06247726a9a6e64823e39ac504 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Thu, 20 Sep 2018 12:16:36 -0700 Subject: [PATCH 21/41] phy: brcm-sata: allow PHY_BRCM_SATA driver to be built for DSL SoCs Broadcom ARM-based DSL SoCs (BCM63xx product line) have the same Broadcom SATA PHY that other SoCs are using, make it possible to select that driver on these platforms. Signed-off-by: Florian Fainelli Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/broadcom/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig index 8786a9674471d..aa917a61071db 100644 --- a/drivers/phy/broadcom/Kconfig +++ b/drivers/phy/broadcom/Kconfig @@ -60,7 +60,8 @@ config PHY_NS2_USB_DRD config PHY_BRCM_SATA tristate "Broadcom SATA PHY driver" - depends on ARCH_BRCMSTB || ARCH_BCM_IPROC || BMIPS_GENERIC || COMPILE_TEST + depends on ARCH_BRCMSTB || ARCH_BCM_IPROC || BMIPS_GENERIC || \ + ARCH_BCM_63XX || COMPILE_TEST depends on OF select GENERIC_PHY default ARCH_BCM_IPROC From 7b69fa1c5c930886f8a916cc47096dd4044c007a Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Thu, 20 Sep 2018 12:16:38 -0700 Subject: [PATCH 22/41] phy: brcm-sata: Add BCM63138 (DSL) PHY init sequence The BCM63138 SATA PHY requires a special initialization sequence in order to operate correctly, mostly tuning incorrect default values. Implement that sequence and match the documented compatible string as an entry point into that sequence. Signed-off-by: Florian Fainelli Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/broadcom/phy-brcm-sata.c | 70 ++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/drivers/phy/broadcom/phy-brcm-sata.c b/drivers/phy/broadcom/phy-brcm-sata.c index ac57f5a41708b..0f4a06ff7fd3b 100644 --- a/drivers/phy/broadcom/phy-brcm-sata.c +++ b/drivers/phy/broadcom/phy-brcm-sata.c @@ -47,6 +47,7 @@ enum brcm_sata_phy_version { BRCM_SATA_PHY_IPROC_NS2, BRCM_SATA_PHY_IPROC_NSP, BRCM_SATA_PHY_IPROC_SR, + BRCM_SATA_PHY_DSL_28NM, }; enum brcm_sata_phy_rxaeq_mode { @@ -96,7 +97,10 @@ enum sata_phy_regs { PLLCONTROL_0_FREQ_DET_RESTART = BIT(13), PLLCONTROL_0_FREQ_MONITOR = BIT(12), PLLCONTROL_0_SEQ_START = BIT(15), + PLL_CAP_CHARGE_TIME = 0x83, + PLL_VCO_CAL_THRESH = 0x84, PLL_CAP_CONTROL = 0x85, + PLL_FREQ_DET_TIME = 0x86, PLL_ACTRL2 = 0x8b, PLL_ACTRL2_SELDIV_MASK = 0x1f, PLL_ACTRL2_SELDIV_SHIFT = 9, @@ -106,6 +110,9 @@ enum sata_phy_regs { PLL1_ACTRL2 = 0x82, PLL1_ACTRL3 = 0x83, PLL1_ACTRL4 = 0x84, + PLL1_ACTRL5 = 0x85, + PLL1_ACTRL6 = 0x86, + PLL1_ACTRL7 = 0x87, TX_REG_BANK = 0x070, TX_ACTRL0 = 0x80, @@ -119,6 +126,8 @@ enum sata_phy_regs { AEQ_FRC_EQ_FORCE = BIT(0), AEQ_FRC_EQ_FORCE_VAL = BIT(1), AEQRX_REG_BANK_1 = 0xe0, + AEQRX_SLCAL0_CTRL0 = 0x82, + AEQRX_SLCAL1_CTRL0 = 0x86, OOB_REG_BANK = 0x150, OOB1_REG_BANK = 0x160, @@ -168,6 +177,7 @@ static inline void __iomem *brcm_sata_pcb_base(struct brcm_sata_port *port) switch (priv->version) { case BRCM_SATA_PHY_STB_28NM: case BRCM_SATA_PHY_IPROC_NS2: + case BRCM_SATA_PHY_DSL_28NM: size = SATA_PCB_REG_28NM_SPACE_SIZE; break; case BRCM_SATA_PHY_STB_40NM: @@ -482,6 +492,61 @@ static int brcm_sr_sata_init(struct brcm_sata_port *port) return 0; } +static int brcm_dsl_sata_init(struct brcm_sata_port *port) +{ + void __iomem *base = brcm_sata_pcb_base(port); + struct device *dev = port->phy_priv->dev; + unsigned int try; + u32 tmp; + + brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL7, 0, 0x873); + + brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL6, 0, 0xc000); + + brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_REG_BANK_0_PLLCONTROL_0, + 0, 0x3089); + usleep_range(1000, 2000); + + brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_REG_BANK_0_PLLCONTROL_0, + 0, 0x3088); + usleep_range(1000, 2000); + + brcm_sata_phy_wr(base, AEQRX_REG_BANK_1, AEQRX_SLCAL0_CTRL0, + 0, 0x3000); + + brcm_sata_phy_wr(base, AEQRX_REG_BANK_1, AEQRX_SLCAL1_CTRL0, + 0, 0x3000); + usleep_range(1000, 2000); + + brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_CAP_CHARGE_TIME, 0, 0x32); + + brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_VCO_CAL_THRESH, 0, 0xa); + + brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_FREQ_DET_TIME, 0, 0x64); + usleep_range(1000, 2000); + + /* Acquire PLL lock */ + try = 50; + while (try) { + tmp = brcm_sata_phy_rd(base, BLOCK0_REG_BANK, + BLOCK0_XGXSSTATUS); + if (tmp & BLOCK0_XGXSSTATUS_PLL_LOCK) + break; + msleep(20); + try--; + }; + + if (!try) { + /* PLL did not lock; give up */ + dev_err(dev, "port%d PLL did not lock\n", port->portnum); + return -ETIMEDOUT; + } + + dev_dbg(dev, "port%d initialized\n", port->portnum); + + return 0; +} + static int brcm_sata_phy_init(struct phy *phy) { int rc; @@ -501,6 +566,9 @@ static int brcm_sata_phy_init(struct phy *phy) case BRCM_SATA_PHY_IPROC_SR: rc = brcm_sr_sata_init(port); break; + case BRCM_SATA_PHY_DSL_28NM: + rc = brcm_dsl_sata_init(port); + break; default: rc = -ENODEV; } @@ -552,6 +620,8 @@ static const struct of_device_id brcm_sata_phy_of_match[] = { .data = (void *)BRCM_SATA_PHY_IPROC_NSP }, { .compatible = "brcm,iproc-sr-sata-phy", .data = (void *)BRCM_SATA_PHY_IPROC_SR }, + { .compatible = "brcm,bcm63138-sata-phy", + .data = (void *)BRCM_SATA_PHY_DSL_28NM }, {}, }; MODULE_DEVICE_TABLE(of, brcm_sata_phy_of_match); From 1582b76286ae6bbfe09a75bdcfc9f5aebc96be9c Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Fri, 21 Sep 2018 20:53:17 +0900 Subject: [PATCH 23/41] dt-bindings: rcar-gen3-phy-usb2: add no-otg-pins property This patch adds a new optional property "renesas,no-otg-pins" which a board does not provide proper otg pins. Signed-off-by: Yoshihiro Shimoda Signed-off-by: Kishon Vijay Abraham I --- Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt index 6a4b5b626b41a..de7b5393c1637 100644 --- a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt +++ b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb2.txt @@ -34,6 +34,8 @@ channel as USB OTG: - interrupts: interrupt specifier for the PHY. - vbus-supply: Phandle to a regulator that provides power to the VBUS. This regulator will be managed during the PHY power on/off sequence. +- renesas,no-otg-pins: boolean, specify when a board does not provide proper + otg pins. Example (R-Car H3): From 09938ea9d136243e8d1fed6d4d7a257764f28f6d Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Fri, 21 Sep 2018 20:53:18 +0900 Subject: [PATCH 24/41] phy: renesas: rcar-gen3-usb2: fix vbus_ctrl for role sysfs This patch fixes and issue that the vbus_ctrl is disabled by rcar_gen3_init_from_a_peri_to_a_host(), so a usb host cannot supply the vbus. Note that this condition will exit when the otg irq happens even if we don't apply this patch. Fixes: 9bb86777fb71 ("phy: rcar-gen3-usb2: add sysfs for usb role swap") Signed-off-by: Yoshihiro Shimoda Reviewed-by: Simon Horman Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/renesas/phy-rcar-gen3-usb2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c index 3d57ea1e1437e..a6db25c20e2cb 100644 --- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -195,7 +195,7 @@ static void rcar_gen3_init_from_a_peri_to_a_host(struct rcar_gen3_chan *ch) val = readl(usb2_base + USB2_OBINTEN); writel(val & ~USB2_OBINT_BITS, usb2_base + USB2_OBINTEN); - rcar_gen3_enable_vbus_ctrl(ch, 0); + rcar_gen3_enable_vbus_ctrl(ch, 1); rcar_gen3_init_for_host(ch); writel(val | USB2_OBINT_BITS, usb2_base + USB2_OBINTEN); From cfdc66348eed22211f277c4bd668ac935b8b3470 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Fri, 21 Sep 2018 20:53:19 +0900 Subject: [PATCH 25/41] phy: renesas: rcar-gen3-usb2: Rename has_otg_pins to uses_otg_pins Since R-Car E3 and D3 have dedicated otg pins actually, "has_otg_pins" is possible to misread in the future. So, this patch renames has_otg_pins to uses_otg_pins. Signed-off-by: Yoshihiro Shimoda Reviewed-by: Simon Horman Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/renesas/phy-rcar-gen3-usb2.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c index a6db25c20e2cb..d69317ea95c9e 100644 --- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -87,7 +87,7 @@ struct rcar_gen3_chan { struct regulator *vbus; struct work_struct work; bool extcon_host; - bool has_otg_pins; + bool uses_otg_pins; }; static void rcar_gen3_phy_usb2_work(struct work_struct *work) @@ -234,7 +234,7 @@ static ssize_t role_store(struct device *dev, struct device_attribute *attr, bool is_b_device; enum phy_mode cur_mode, new_mode; - if (!ch->has_otg_pins || !ch->phy->init_count) + if (!ch->uses_otg_pins || !ch->phy->init_count) return -EIO; if (!strncmp(buf, "host", strlen("host"))) @@ -272,7 +272,7 @@ static ssize_t role_show(struct device *dev, struct device_attribute *attr, { struct rcar_gen3_chan *ch = dev_get_drvdata(dev); - if (!ch->has_otg_pins || !ch->phy->init_count) + if (!ch->uses_otg_pins || !ch->phy->init_count) return -EIO; return sprintf(buf, "%s\n", rcar_gen3_is_host(ch) ? "host" : @@ -311,7 +311,7 @@ static int rcar_gen3_phy_usb2_init(struct phy *p) writel(USB2_OC_TIMSET_INIT, usb2_base + USB2_OC_TIMSET); /* Initialize otg part */ - if (channel->has_otg_pins) + if (channel->uses_otg_pins) rcar_gen3_init_otg(channel); return 0; @@ -445,7 +445,7 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) if (of_usb_get_dr_mode_by_phy(dev->of_node, 0) == USB_DR_MODE_OTG) { int ret; - channel->has_otg_pins = (uintptr_t)of_device_get_match_data(dev); + channel->uses_otg_pins = (uintptr_t)of_device_get_match_data(dev); channel->extcon = devm_extcon_dev_allocate(dev, rcar_gen3_phy_cable); if (IS_ERR(channel->extcon)) @@ -487,7 +487,7 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) dev_err(dev, "Failed to register PHY provider\n"); ret = PTR_ERR(provider); goto error; - } else if (channel->has_otg_pins) { + } else if (channel->uses_otg_pins) { int ret; ret = device_create_file(dev, &dev_attr_role); @@ -507,7 +507,7 @@ static int rcar_gen3_phy_usb2_remove(struct platform_device *pdev) { struct rcar_gen3_chan *channel = platform_get_drvdata(pdev); - if (channel->has_otg_pins) + if (channel->uses_otg_pins) device_remove_file(&pdev->dev, &dev_attr_role); pm_runtime_disable(&pdev->dev); From 8dde0008ffc9e2e214ef916821959fe6706ff9f0 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Fri, 21 Sep 2018 20:53:20 +0900 Subject: [PATCH 26/41] phy: renesas: rcar-gen3-usb2: Check a property to use otg pins Since All R-Car Gen3 SoCs have dedicated otg pins actually but some boards don't use the otg pins (e.g. R-Car D3 Draak and R-Car E3 Ebisu), the driver should not choose SoC model base by using rcar_gen3_phy_usb2_match_table's data. So, this patch checks a "renesas,no-otg-pins" property to set the "uses_otg_pins". Note that since r8a77995-draak.dts and r8a77990-ebisu.dts don't have 'dr_mode = "otg";' for now, if we apply this patch, no behavior changes (the value of "uses_otg_pins" is false). Signed-off-by: Yoshihiro Shimoda Reviewed-by: Simon Horman Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/renesas/phy-rcar-gen3-usb2.c | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c index d69317ea95c9e..856056e14b26d 100644 --- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -78,8 +78,6 @@ #define USB2_ADPCTRL_IDPULLUP BIT(5) /* 1 = ID sampling is enabled */ #define USB2_ADPCTRL_DRVVBUS BIT(4) -#define RCAR_GEN3_PHY_HAS_DEDICATED_PINS 1 - struct rcar_gen3_chan { void __iomem *base; struct extcon_dev *extcon; @@ -385,21 +383,10 @@ static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch) } static const struct of_device_id rcar_gen3_phy_usb2_match_table[] = { - { - .compatible = "renesas,usb2-phy-r8a7795", - .data = (void *)RCAR_GEN3_PHY_HAS_DEDICATED_PINS, - }, - { - .compatible = "renesas,usb2-phy-r8a7796", - .data = (void *)RCAR_GEN3_PHY_HAS_DEDICATED_PINS, - }, - { - .compatible = "renesas,usb2-phy-r8a77965", - .data = (void *)RCAR_GEN3_PHY_HAS_DEDICATED_PINS, - }, - { - .compatible = "renesas,rcar-gen3-usb2-phy", - }, + { .compatible = "renesas,usb2-phy-r8a7795" }, + { .compatible = "renesas,usb2-phy-r8a7796" }, + { .compatible = "renesas,usb2-phy-r8a77965" }, + { .compatible = "renesas,rcar-gen3-usb2-phy" }, { } }; MODULE_DEVICE_TABLE(of, rcar_gen3_phy_usb2_match_table); @@ -445,7 +432,8 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) if (of_usb_get_dr_mode_by_phy(dev->of_node, 0) == USB_DR_MODE_OTG) { int ret; - channel->uses_otg_pins = (uintptr_t)of_device_get_match_data(dev); + channel->uses_otg_pins = !of_property_read_bool(dev->of_node, + "renesas,no-otg-pins"); channel->extcon = devm_extcon_dev_allocate(dev, rcar_gen3_phy_cable); if (IS_ERR(channel->extcon)) From 7ab0305d4d7725699169e21cdc4f6c8759c32feb Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Fri, 21 Sep 2018 20:53:21 +0900 Subject: [PATCH 27/41] phy: renesas: rcar-gen3-usb2: unify OBINTEN handling This patch unifies the OBINTEN handling to clean-up the code. Signed-off-by: Yoshihiro Shimoda Reviewed-by: Simon Horman Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/renesas/phy-rcar-gen3-usb2.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c index 856056e14b26d..e7eaed9549a39 100644 --- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -142,6 +142,18 @@ static void rcar_gen3_enable_vbus_ctrl(struct rcar_gen3_chan *ch, int vbus) writel(val, usb2_base + USB2_ADPCTRL); } +static void rcar_gen3_control_otg_irq(struct rcar_gen3_chan *ch, int enable) +{ + void __iomem *usb2_base = ch->base; + u32 val = readl(usb2_base + USB2_OBINTEN); + + if (enable) + val |= USB2_OBINT_BITS; + else + val &= ~USB2_OBINT_BITS; + writel(val, usb2_base + USB2_OBINTEN); +} + static void rcar_gen3_init_for_host(struct rcar_gen3_chan *ch) { rcar_gen3_set_linectrl(ch, 1, 1); @@ -187,16 +199,12 @@ static void rcar_gen3_init_for_a_peri(struct rcar_gen3_chan *ch) static void rcar_gen3_init_from_a_peri_to_a_host(struct rcar_gen3_chan *ch) { - void __iomem *usb2_base = ch->base; - u32 val; - - val = readl(usb2_base + USB2_OBINTEN); - writel(val & ~USB2_OBINT_BITS, usb2_base + USB2_OBINTEN); + rcar_gen3_control_otg_irq(ch, 0); rcar_gen3_enable_vbus_ctrl(ch, 1); rcar_gen3_init_for_host(ch); - writel(val | USB2_OBINT_BITS, usb2_base + USB2_OBINTEN); + rcar_gen3_control_otg_irq(ch, 1); } static bool rcar_gen3_check_id(struct rcar_gen3_chan *ch) @@ -286,8 +294,7 @@ static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch) val = readl(usb2_base + USB2_VBCTRL); writel(val | USB2_VBCTRL_DRVVBUSSEL, usb2_base + USB2_VBCTRL); writel(USB2_OBINT_BITS, usb2_base + USB2_OBINTSTA); - val = readl(usb2_base + USB2_OBINTEN); - writel(val | USB2_OBINT_BITS, usb2_base + USB2_OBINTEN); + rcar_gen3_control_otg_irq(ch, 1); val = readl(usb2_base + USB2_ADPCTRL); writel(val | USB2_ADPCTRL_IDPULLUP, usb2_base + USB2_ADPCTRL); val = readl(usb2_base + USB2_LINECTRL1); From 73801b90a38ff1ee89ad38c925fa37fbcb421bfc Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Fri, 21 Sep 2018 20:53:22 +0900 Subject: [PATCH 28/41] phy: renesas: rcar-gen3-usb2: change a condition "dr_mode" This patch changes a condition about dr_mode. If a device node has any dr_mode ("host", "peripheral" or "otg"), this driver allows to set "is_otg_channel" to true. Also, this patch keeps the dr_mode value for future use. Signed-off-by: Yoshihiro Shimoda Reviewed-by: Simon Horman Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/renesas/phy-rcar-gen3-usb2.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c index e7eaed9549a39..93ab860f09beb 100644 --- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -84,6 +84,7 @@ struct rcar_gen3_chan { struct phy *phy; struct regulator *vbus; struct work_struct work; + enum usb_dr_mode dr_mode; bool extcon_host; bool uses_otg_pins; }; @@ -436,7 +437,8 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) dev_err(dev, "No irq handler (%d)\n", irq); } - if (of_usb_get_dr_mode_by_phy(dev->of_node, 0) == USB_DR_MODE_OTG) { + channel->dr_mode = of_usb_get_dr_mode_by_phy(dev->of_node, 0); + if (channel->dr_mode != USB_DR_MODE_UNKNOWN) { int ret; channel->uses_otg_pins = !of_property_read_bool(dev->of_node, From a602152c81a2c1c0daeae56d145d558d52a70dae Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Fri, 21 Sep 2018 20:53:23 +0900 Subject: [PATCH 29/41] phy: renesas: rcar-gen3-usb2: add conditions for uses_otg_pins == false If uses_otg_pins is set to false, this driver 1) should disable otg related interruptions, and 2) should not get ID pin signal, to avoid unexpected behaviors. So, this patch adds conditions for it. Signed-off-by: Yoshihiro Shimoda Reviewed-by: Simon Horman Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/renesas/phy-rcar-gen3-usb2.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c index 93ab860f09beb..3f2efe55940c6 100644 --- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -148,7 +148,7 @@ static void rcar_gen3_control_otg_irq(struct rcar_gen3_chan *ch, int enable) void __iomem *usb2_base = ch->base; u32 val = readl(usb2_base + USB2_OBINTEN); - if (enable) + if (ch->uses_otg_pins && enable) val |= USB2_OBINT_BITS; else val &= ~USB2_OBINT_BITS; @@ -210,6 +210,9 @@ static void rcar_gen3_init_from_a_peri_to_a_host(struct rcar_gen3_chan *ch) static bool rcar_gen3_check_id(struct rcar_gen3_chan *ch) { + if (!ch->uses_otg_pins) + return (ch->dr_mode == USB_DR_MODE_HOST) ? false : true; + return !!(readl(ch->base + USB2_ADPCTRL) & USB2_ADPCTRL_IDDIG); } From 979b519c7a1bff56a212bf80cc04ae9b4131ea43 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Fri, 21 Sep 2018 20:53:24 +0900 Subject: [PATCH 30/41] phy: renesas: rcar-gen3-usb2: add is_otg_channel to use "role" sysfs Even if a board doesn't have otg pins connection, this hardware can change the role by a register setting. So, this patch adds "is_otg_channel" for it. Signed-off-by: Yoshihiro Shimoda Reviewed-by: Simon Horman Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/renesas/phy-rcar-gen3-usb2.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c index 3f2efe55940c6..d0f412c25981e 100644 --- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -86,9 +86,21 @@ struct rcar_gen3_chan { struct work_struct work; enum usb_dr_mode dr_mode; bool extcon_host; + bool is_otg_channel; bool uses_otg_pins; }; +/* + * Combination about is_otg_channel and uses_otg_pins: + * + * Parameters || Behaviors + * is_otg_channel | uses_otg_pins || irqs | role sysfs + * ---------------------+---------------++--------------+------------ + * true | true || enabled | enabled + * true | false || disabled | enabled + * false | any || disabled | disabled + */ + static void rcar_gen3_phy_usb2_work(struct work_struct *work) { struct rcar_gen3_chan *ch = container_of(work, struct rcar_gen3_chan, @@ -244,7 +256,7 @@ static ssize_t role_store(struct device *dev, struct device_attribute *attr, bool is_b_device; enum phy_mode cur_mode, new_mode; - if (!ch->uses_otg_pins || !ch->phy->init_count) + if (!ch->is_otg_channel || !ch->phy->init_count) return -EIO; if (!strncmp(buf, "host", strlen("host"))) @@ -282,7 +294,7 @@ static ssize_t role_show(struct device *dev, struct device_attribute *attr, { struct rcar_gen3_chan *ch = dev_get_drvdata(dev); - if (!ch->uses_otg_pins || !ch->phy->init_count) + if (!ch->is_otg_channel || !ch->phy->init_count) return -EIO; return sprintf(buf, "%s\n", rcar_gen3_is_host(ch) ? "host" : @@ -320,7 +332,7 @@ static int rcar_gen3_phy_usb2_init(struct phy *p) writel(USB2_OC_TIMSET_INIT, usb2_base + USB2_OC_TIMSET); /* Initialize otg part */ - if (channel->uses_otg_pins) + if (channel->is_otg_channel) rcar_gen3_init_otg(channel); return 0; @@ -444,6 +456,7 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) if (channel->dr_mode != USB_DR_MODE_UNKNOWN) { int ret; + channel->is_otg_channel = true; channel->uses_otg_pins = !of_property_read_bool(dev->of_node, "renesas,no-otg-pins"); channel->extcon = devm_extcon_dev_allocate(dev, @@ -487,7 +500,7 @@ static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) dev_err(dev, "Failed to register PHY provider\n"); ret = PTR_ERR(provider); goto error; - } else if (channel->uses_otg_pins) { + } else if (channel->is_otg_channel) { int ret; ret = device_create_file(dev, &dev_attr_role); @@ -507,7 +520,7 @@ static int rcar_gen3_phy_usb2_remove(struct platform_device *pdev) { struct rcar_gen3_chan *channel = platform_get_drvdata(pdev); - if (channel->uses_otg_pins) + if (channel->is_otg_channel) device_remove_file(&pdev->dev, &dev_attr_role); pm_runtime_disable(&pdev->dev); From 6c7103aa026094a4ee2c2708ec6977a6dfc5331d Mon Sep 17 00:00:00 2001 From: Andreas Kemnade Date: Sat, 22 Sep 2018 11:44:05 +0200 Subject: [PATCH 31/41] phy: phy-twl4030-usb: fix denied runtime access When runtime is not enabled, pm_runtime_get_sync() returns -EACCESS, the counter will be incremented but the resume callback not called, so enumeration and charging will not start properly. To avoid that happen, disable irq on suspend and recheck on resume. Practically this happens when the device is woken up from suspend by plugging in usb. Signed-off-by: Andreas Kemnade Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/ti/phy-twl4030-usb.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/phy/ti/phy-twl4030-usb.c b/drivers/phy/ti/phy-twl4030-usb.c index a44680d64f9b4..c267afb68f077 100644 --- a/drivers/phy/ti/phy-twl4030-usb.c +++ b/drivers/phy/ti/phy-twl4030-usb.c @@ -144,6 +144,7 @@ #define PMBR1 0x0D #define GPIO_USB_4PIN_ULPI_2430C (3 << 0) +static irqreturn_t twl4030_usb_irq(int irq, void *_twl); /* * If VBUS is valid or ID is ground, then we know a * cable is present and we need to be runtime-enabled @@ -395,6 +396,33 @@ static void __twl4030_phy_power(struct twl4030_usb *twl, int on) WARN_ON(twl4030_usb_write_verify(twl, PHY_PWR_CTRL, pwr) < 0); } +static int __maybe_unused twl4030_usb_suspend(struct device *dev) +{ + struct twl4030_usb *twl = dev_get_drvdata(dev); + + /* + * we need enabled runtime on resume, + * so turn irq off here, so we do not get it early + * note: wakeup on usb plug works independently of this + */ + dev_dbg(twl->dev, "%s\n", __func__); + disable_irq(twl->irq); + + return 0; +} + +static int __maybe_unused twl4030_usb_resume(struct device *dev) +{ + struct twl4030_usb *twl = dev_get_drvdata(dev); + + dev_dbg(twl->dev, "%s\n", __func__); + enable_irq(twl->irq); + /* check whether cable status changed */ + twl4030_usb_irq(0, twl); + + return 0; +} + static int __maybe_unused twl4030_usb_runtime_suspend(struct device *dev) { struct twl4030_usb *twl = dev_get_drvdata(dev); @@ -655,6 +683,7 @@ static const struct phy_ops ops = { static const struct dev_pm_ops twl4030_usb_pm_ops = { SET_RUNTIME_PM_OPS(twl4030_usb_runtime_suspend, twl4030_usb_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(twl4030_usb_suspend, twl4030_usb_resume) }; static int twl4030_usb_probe(struct platform_device *pdev) From 63bd0f19226dea7c373e961d7aebe4c5d8798ccc Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Tue, 28 Aug 2018 10:56:10 +0200 Subject: [PATCH 32/41] phy: rockchip-usb: add usb-uart setup for rk3188 The rk3188 also supports bringing the uart2 out through the usb dm+dp pins, so add the necessary setup for it. rk3066 does not seem to support usb-uart functionality and this particular phy was only used on older Rockchip socs, so this leaves room for a bit of cleanup as well, as there most likely won't be new additions in the driver. Signed-off-by: Heiko Stuebner Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/rockchip/phy-rockchip-usb.c | 141 ++++++++++++++++-------- 1 file changed, 95 insertions(+), 46 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-usb.c b/drivers/phy/rockchip/phy-rockchip-usb.c index 269c86329cb86..b2899c744ad9f 100644 --- a/drivers/phy/rockchip/phy-rockchip-usb.c +++ b/drivers/phy/rockchip/phy-rockchip-usb.c @@ -36,7 +36,22 @@ static int enable_usb_uart; #define HIWORD_UPDATE(val, mask) \ ((val) | (mask) << 16) -#define UOC_CON0_SIDDQ BIT(13) +#define UOC_CON0 0x00 +#define UOC_CON0_SIDDQ BIT(13) +#define UOC_CON0_DISABLE BIT(4) +#define UOC_CON0_COMMON_ON_N BIT(0) + +#define UOC_CON2 0x08 +#define UOC_CON2_SOFT_CON_SEL BIT(2) + +#define UOC_CON3 0x0c +/* bits present on rk3188 and rk3288 phys */ +#define UOC_CON3_UTMI_TERMSEL_FULLSPEED BIT(5) +#define UOC_CON3_UTMI_XCVRSEELCT_FSTRANSC (1 << 3) +#define UOC_CON3_UTMI_XCVRSEELCT_MASK (3 << 3) +#define UOC_CON3_UTMI_OPMODE_NODRIVING (1 << 1) +#define UOC_CON3_UTMI_OPMODE_MASK (3 << 1) +#define UOC_CON3_UTMI_SUSPENDN BIT(0) struct rockchip_usb_phys { int reg; @@ -46,7 +61,8 @@ struct rockchip_usb_phys { struct rockchip_usb_phy_base; struct rockchip_usb_phy_pdata { struct rockchip_usb_phys *phys; - int (*init_usb_uart)(struct regmap *grf); + int (*init_usb_uart)(struct regmap *grf, + const struct rockchip_usb_phy_pdata *pdata); int usb_uart_phy; }; @@ -313,28 +329,88 @@ static const struct rockchip_usb_phy_pdata rk3066a_pdata = { }, }; +static int __init rockchip_init_usb_uart_common(struct regmap *grf, + const struct rockchip_usb_phy_pdata *pdata) +{ + int regoffs = pdata->phys[pdata->usb_uart_phy].reg; + int ret; + u32 val; + + /* + * COMMON_ON and DISABLE settings are described in the TRM, + * but were not present in the original code. + * Also disable the analog phy components to save power. + */ + val = HIWORD_UPDATE(UOC_CON0_COMMON_ON_N + | UOC_CON0_DISABLE + | UOC_CON0_SIDDQ, + UOC_CON0_COMMON_ON_N + | UOC_CON0_DISABLE + | UOC_CON0_SIDDQ); + ret = regmap_write(grf, regoffs + UOC_CON0, val); + if (ret) + return ret; + + val = HIWORD_UPDATE(UOC_CON2_SOFT_CON_SEL, + UOC_CON2_SOFT_CON_SEL); + ret = regmap_write(grf, regoffs + UOC_CON2, val); + if (ret) + return ret; + + val = HIWORD_UPDATE(UOC_CON3_UTMI_OPMODE_NODRIVING + | UOC_CON3_UTMI_XCVRSEELCT_FSTRANSC + | UOC_CON3_UTMI_TERMSEL_FULLSPEED, + UOC_CON3_UTMI_SUSPENDN + | UOC_CON3_UTMI_OPMODE_MASK + | UOC_CON3_UTMI_XCVRSEELCT_MASK + | UOC_CON3_UTMI_TERMSEL_FULLSPEED); + ret = regmap_write(grf, UOC_CON3, val); + if (ret) + return ret; + + return 0; +} + +#define RK3188_UOC0_CON0 0x10c +#define RK3188_UOC0_CON0_BYPASSSEL BIT(9) +#define RK3188_UOC0_CON0_BYPASSDMEN BIT(8) + +/* + * Enable the bypass of uart2 data through the otg usb phy. + * See description of rk3288-variant for details. + */ +static int __init rk3188_init_usb_uart(struct regmap *grf, + const struct rockchip_usb_phy_pdata *pdata) +{ + u32 val; + int ret; + + ret = rockchip_init_usb_uart_common(grf, pdata); + if (ret) + return ret; + + val = HIWORD_UPDATE(RK3188_UOC0_CON0_BYPASSSEL + | RK3188_UOC0_CON0_BYPASSDMEN, + RK3188_UOC0_CON0_BYPASSSEL + | RK3188_UOC0_CON0_BYPASSDMEN); + ret = regmap_write(grf, RK3188_UOC0_CON0, val); + if (ret) + return ret; + + return 0; +} + static const struct rockchip_usb_phy_pdata rk3188_pdata = { .phys = (struct rockchip_usb_phys[]){ { .reg = 0x10c, .pll_name = "sclk_otgphy0_480m" }, { .reg = 0x11c, .pll_name = "sclk_otgphy1_480m" }, { /* sentinel */ } }, + .init_usb_uart = rk3188_init_usb_uart, + .usb_uart_phy = 0, }; -#define RK3288_UOC0_CON0 0x320 -#define RK3288_UOC0_CON0_COMMON_ON_N BIT(0) -#define RK3288_UOC0_CON0_DISABLE BIT(4) - -#define RK3288_UOC0_CON2 0x328 -#define RK3288_UOC0_CON2_SOFT_CON_SEL BIT(2) - #define RK3288_UOC0_CON3 0x32c -#define RK3288_UOC0_CON3_UTMI_SUSPENDN BIT(0) -#define RK3288_UOC0_CON3_UTMI_OPMODE_NODRIVING (1 << 1) -#define RK3288_UOC0_CON3_UTMI_OPMODE_MASK (3 << 1) -#define RK3288_UOC0_CON3_UTMI_XCVRSEELCT_FSTRANSC (1 << 3) -#define RK3288_UOC0_CON3_UTMI_XCVRSEELCT_MASK (3 << 3) -#define RK3288_UOC0_CON3_UTMI_TERMSEL_FULLSPEED BIT(5) #define RK3288_UOC0_CON3_BYPASSDMEN BIT(6) #define RK3288_UOC0_CON3_BYPASSSEL BIT(7) @@ -353,40 +429,13 @@ static const struct rockchip_usb_phy_pdata rk3188_pdata = { * * The actual code in the vendor kernel does some things differently. */ -static int __init rk3288_init_usb_uart(struct regmap *grf) +static int __init rk3288_init_usb_uart(struct regmap *grf, + const struct rockchip_usb_phy_pdata *pdata) { u32 val; int ret; - /* - * COMMON_ON and DISABLE settings are described in the TRM, - * but were not present in the original code. - * Also disable the analog phy components to save power. - */ - val = HIWORD_UPDATE(RK3288_UOC0_CON0_COMMON_ON_N - | RK3288_UOC0_CON0_DISABLE - | UOC_CON0_SIDDQ, - RK3288_UOC0_CON0_COMMON_ON_N - | RK3288_UOC0_CON0_DISABLE - | UOC_CON0_SIDDQ); - ret = regmap_write(grf, RK3288_UOC0_CON0, val); - if (ret) - return ret; - - val = HIWORD_UPDATE(RK3288_UOC0_CON2_SOFT_CON_SEL, - RK3288_UOC0_CON2_SOFT_CON_SEL); - ret = regmap_write(grf, RK3288_UOC0_CON2, val); - if (ret) - return ret; - - val = HIWORD_UPDATE(RK3288_UOC0_CON3_UTMI_OPMODE_NODRIVING - | RK3288_UOC0_CON3_UTMI_XCVRSEELCT_FSTRANSC - | RK3288_UOC0_CON3_UTMI_TERMSEL_FULLSPEED, - RK3288_UOC0_CON3_UTMI_SUSPENDN - | RK3288_UOC0_CON3_UTMI_OPMODE_MASK - | RK3288_UOC0_CON3_UTMI_XCVRSEELCT_MASK - | RK3288_UOC0_CON3_UTMI_TERMSEL_FULLSPEED); - ret = regmap_write(grf, RK3288_UOC0_CON3, val); + ret = rockchip_init_usb_uart_common(grf, pdata); if (ret) return ret; @@ -516,7 +565,7 @@ static int __init rockchip_init_usb_uart(void) return PTR_ERR(grf); } - ret = data->init_usb_uart(grf); + ret = data->init_usb_uart(grf, data); if (ret) { pr_err("%s: could not init usb_uart, %d\n", __func__, ret); enable_usb_uart = 0; From 0d58280cf1e61b06cb4d4aab672efccdc28794f6 Mon Sep 17 00:00:00 2001 From: Can Guo Date: Thu, 20 Sep 2018 21:27:54 -0700 Subject: [PATCH 33/41] phy: Update PHY power control sequence All PHYs should be powered on before register configuration starts. And only PCIe PHYs need an extra power control before deasserts reset state. Signed-off-by: Can Guo Reviewed-by: Manu Gautam Reviewed-by: Vivek Gautam Reviewed-by: Evan Green Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/qualcomm/phy-qcom-qmp.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.c b/drivers/phy/qualcomm/phy-qcom-qmp.c index 7932931a3da30..a55cfa4554a00 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp.c @@ -935,10 +935,12 @@ static void qcom_qmp_phy_configure(void __iomem *base, } } -static int qcom_qmp_phy_com_init(struct qcom_qmp *qmp) +static int qcom_qmp_phy_com_init(struct qmp_phy *qphy) { + struct qcom_qmp *qmp = qphy->qmp; const struct qmp_phy_cfg *cfg = qmp->cfg; void __iomem *serdes = qmp->serdes; + void __iomem *pcs = qphy->pcs; void __iomem *dp_com = qmp->dp_com; int ret, i; @@ -979,10 +981,6 @@ static int qcom_qmp_phy_com_init(struct qcom_qmp *qmp) goto err_rst; } - if (cfg->has_phy_com_ctrl) - qphy_setbits(serdes, cfg->regs[QPHY_COM_POWER_DOWN_CONTROL], - SW_PWRDN); - if (cfg->has_phy_dp_com_ctrl) { qphy_setbits(dp_com, QPHY_V3_DP_COM_POWER_DOWN_CTRL, SW_PWRDN); @@ -1000,6 +998,12 @@ static int qcom_qmp_phy_com_init(struct qcom_qmp *qmp) SW_USB3PHY_RESET_MUX | SW_USB3PHY_RESET); } + if (cfg->has_phy_com_ctrl) + qphy_setbits(serdes, cfg->regs[QPHY_COM_POWER_DOWN_CONTROL], + SW_PWRDN); + else + qphy_setbits(pcs, QPHY_POWER_DOWN_CONTROL, cfg->pwrdn_ctrl); + /* Serdes configuration */ qcom_qmp_phy_configure(serdes, cfg->regs, cfg->serdes_tbl, cfg->serdes_tbl_num); @@ -1090,7 +1094,7 @@ static int qcom_qmp_phy_init(struct phy *phy) dev_vdbg(qmp->dev, "Initializing QMP phy\n"); - ret = qcom_qmp_phy_com_init(qmp); + ret = qcom_qmp_phy_com_init(qphy); if (ret) return ret; @@ -1127,7 +1131,8 @@ static int qcom_qmp_phy_init(struct phy *phy) * Pull out PHY from POWER DOWN state. * This is active low enable signal to power-down PHY. */ - qphy_setbits(pcs, QPHY_POWER_DOWN_CONTROL, cfg->pwrdn_ctrl); + if(cfg->type == PHY_TYPE_PCIE) + qphy_setbits(pcs, QPHY_POWER_DOWN_CONTROL, cfg->pwrdn_ctrl); if (cfg->has_pwrdn_delay) usleep_range(cfg->pwrdn_delay_min, cfg->pwrdn_delay_max); From 6b04526812ac41ba82317caa8df3549dda2cab97 Mon Sep 17 00:00:00 2001 From: Can Guo Date: Thu, 20 Sep 2018 21:27:55 -0700 Subject: [PATCH 34/41] phy: General struct and field cleanup Move MSM8996 specific PHY vreg list struct name to a genernal one as it is used by all PHYs. Add a specific field to handle dual lane situation. Signed-off-by: Can Guo Reviewed-by: Evan Green Reviewed-by: Manu Gautam Reviewed-by: Vivek Gautam Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/qualcomm/phy-qcom-qmp.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.c b/drivers/phy/qualcomm/phy-qcom-qmp.c index a55cfa4554a00..1603d9c615c53 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp.c @@ -649,6 +649,8 @@ struct qmp_phy_cfg { /* true, if PHY has a separate DP_COM control block */ bool has_phy_dp_com_ctrl; + /* true, if PHY has secondary tx/rx lanes to be configured */ + bool is_dual_lane_phy; /* Register offset of secondary tx/rx lanes for USB DP combo PHY */ unsigned int tx_b_lane_offset; unsigned int rx_b_lane_offset; @@ -758,7 +760,7 @@ static const char * const msm8996_usb3phy_reset_l[] = { }; /* list of regulators */ -static const char * const msm8996_phy_vreg_l[] = { +static const char * const qmp_phy_vreg_l[] = { "vdda-phy", "vdda-pll", }; @@ -778,8 +780,8 @@ static const struct qmp_phy_cfg msm8996_pciephy_cfg = { .num_clks = ARRAY_SIZE(msm8996_phy_clk_l), .reset_list = msm8996_pciephy_reset_l, .num_resets = ARRAY_SIZE(msm8996_pciephy_reset_l), - .vreg_list = msm8996_phy_vreg_l, - .num_vregs = ARRAY_SIZE(msm8996_phy_vreg_l), + .vreg_list = qmp_phy_vreg_l, + .num_vregs = ARRAY_SIZE(qmp_phy_vreg_l), .regs = pciephy_regs_layout, .start_ctrl = PCS_START | PLL_READY_GATE_EN, @@ -809,8 +811,8 @@ static const struct qmp_phy_cfg msm8996_usb3phy_cfg = { .num_clks = ARRAY_SIZE(msm8996_phy_clk_l), .reset_list = msm8996_usb3phy_reset_l, .num_resets = ARRAY_SIZE(msm8996_usb3phy_reset_l), - .vreg_list = msm8996_phy_vreg_l, - .num_vregs = ARRAY_SIZE(msm8996_phy_vreg_l), + .vreg_list = qmp_phy_vreg_l, + .num_vregs = ARRAY_SIZE(qmp_phy_vreg_l), .regs = usb3phy_regs_layout, .start_ctrl = SERDES_START | PCS_START, @@ -870,8 +872,8 @@ static const struct qmp_phy_cfg qmp_v3_usb3phy_cfg = { .num_clks = ARRAY_SIZE(qmp_v3_phy_clk_l), .reset_list = msm8996_usb3phy_reset_l, .num_resets = ARRAY_SIZE(msm8996_usb3phy_reset_l), - .vreg_list = msm8996_phy_vreg_l, - .num_vregs = ARRAY_SIZE(msm8996_phy_vreg_l), + .vreg_list = qmp_phy_vreg_l, + .num_vregs = ARRAY_SIZE(qmp_phy_vreg_l), .regs = qmp_v3_usb3phy_regs_layout, .start_ctrl = SERDES_START | PCS_START, @@ -883,6 +885,7 @@ static const struct qmp_phy_cfg qmp_v3_usb3phy_cfg = { .pwrdn_delay_max = POWER_DOWN_DELAY_US_MAX, .has_phy_dp_com_ctrl = true, + .is_dual_lane_phy = true, .tx_b_lane_offset = 0x400, .rx_b_lane_offset = 0x400, }; @@ -903,8 +906,8 @@ static const struct qmp_phy_cfg qmp_v3_usb3_uniphy_cfg = { .num_clks = ARRAY_SIZE(qmp_v3_phy_clk_l), .reset_list = msm8996_usb3phy_reset_l, .num_resets = ARRAY_SIZE(msm8996_usb3phy_reset_l), - .vreg_list = msm8996_phy_vreg_l, - .num_vregs = ARRAY_SIZE(msm8996_phy_vreg_l), + .vreg_list = qmp_phy_vreg_l, + .num_vregs = ARRAY_SIZE(qmp_phy_vreg_l), .regs = qmp_v3_usb3phy_regs_layout, .start_ctrl = SERDES_START | PCS_START, @@ -1116,12 +1119,12 @@ static int qcom_qmp_phy_init(struct phy *phy) /* Tx, Rx, and PCS configurations */ qcom_qmp_phy_configure(tx, cfg->regs, cfg->tx_tbl, cfg->tx_tbl_num); /* Configuration for other LANE for USB-DP combo PHY */ - if (cfg->has_phy_dp_com_ctrl) + if (cfg->is_dual_lane_phy) qcom_qmp_phy_configure(tx + cfg->tx_b_lane_offset, cfg->regs, cfg->tx_tbl, cfg->tx_tbl_num); qcom_qmp_phy_configure(rx, cfg->regs, cfg->rx_tbl, cfg->rx_tbl_num); - if (cfg->has_phy_dp_com_ctrl) + if (cfg->is_dual_lane_phy) qcom_qmp_phy_configure(rx + cfg->rx_b_lane_offset, cfg->regs, cfg->rx_tbl, cfg->rx_tbl_num); From cc31cdbef9b7166fe42e08267349cfbaa32696b6 Mon Sep 17 00:00:00 2001 From: Can Guo Date: Thu, 20 Sep 2018 21:27:56 -0700 Subject: [PATCH 35/41] phy: Add QMP phy based UFS phy support for sdm845 Add UFS PHY support to make SDM845 UFS work with common PHY framework. Signed-off-by: Can Guo Reviewed-by: Evan Green Reviewed-by: Vivek Gautam Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/qualcomm/phy-qcom-qmp.c | 172 +++++++++++++++++++++++++++- drivers/phy/qualcomm/phy-qcom-qmp.h | 15 +++ 2 files changed, 186 insertions(+), 1 deletion(-) diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.c b/drivers/phy/qualcomm/phy-qcom-qmp.c index 1603d9c615c53..a833324110264 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp.c @@ -156,6 +156,11 @@ static const unsigned int qmp_v3_usb3phy_regs_layout[] = { [QPHY_PCS_LFPS_RXTERM_IRQ_STATUS] = 0x170, }; +static const unsigned int sdm845_ufsphy_regs_layout[] = { + [QPHY_START_CTRL] = 0x00, + [QPHY_PCS_READY_STATUS] = 0x160, +}; + static const struct qmp_phy_init_tbl msm8996_pcie_serdes_tbl[] = { QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x1c), QMP_PHY_INIT_CFG(QSERDES_COM_CLK_ENABLE1, 0x10), @@ -601,6 +606,83 @@ static const struct qmp_phy_init_tbl qmp_v3_usb3_uniphy_pcs_tbl[] = { QMP_PHY_INIT_CFG(QPHY_V3_PCS_REFGEN_REQ_CONFIG2, 0x60), }; +static const struct qmp_phy_init_tbl sdm845_ufsphy_serdes_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYS_CLK_CTRL, 0x02), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_BIAS_EN_CLKBUFLR_EN, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_BG_TIMER, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_IVCO, 0x07), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CMN_CONFIG, 0x06), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYSCLK_EN_SEL, 0xd5), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_RESETSM_CNTRL, 0x20), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_SELECT, 0x30), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_HSCLK_SEL, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP_EN, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE_CTRL, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CORE_CLK_EN, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE_MAP, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SVS_MODE_CLK_SEL, 0x05), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE_INITVAL1, 0xff), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE_INITVAL2, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_DEC_START_MODE0, 0x82), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CP_CTRL_MODE0, 0x06), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_RCTRL_MODE0, 0x16), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_CCTRL_MODE0, 0x36), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_INTEGLOOP_GAIN0_MODE0, 0x3f), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_INTEGLOOP_GAIN1_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE1_MODE0, 0xda), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE2_MODE0, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP1_MODE0, 0xff), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP2_MODE0, 0x0c), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_DEC_START_MODE1, 0x98), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CP_CTRL_MODE1, 0x06), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_RCTRL_MODE1, 0x16), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_CCTRL_MODE1, 0x36), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_INTEGLOOP_GAIN0_MODE1, 0x3f), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_INTEGLOOP_GAIN1_MODE1, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE1_MODE1, 0xc1), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE2_MODE1, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP1_MODE1, 0x32), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP2_MODE1, 0x0f), + + /* Rate B */ + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE_MAP, 0x44), +}; + +static const struct qmp_phy_init_tbl sdm845_ufsphy_tx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V3_TX_LANE_MODE_1, 0x06), + QMP_PHY_INIT_CFG(QSERDES_V3_TX_RES_CODE_LANE_OFFSET_TX, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V3_TX_RES_CODE_LANE_OFFSET_RX, 0x07), +}; + +static const struct qmp_phy_init_tbl sdm845_ufsphy_rx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_LVL, 0x24), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_CNTRL, 0x0f), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_DEGLITCH_CNTRL, 0x1e), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_INTERFACE_MODE, 0x40), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_FASTLOCK_FO_GAIN, 0x0b), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_TERM_BW, 0x5b), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL2, 0x06), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL3, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL4, 0x1b), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SVS_SO_GAIN_HALF, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SVS_SO_GAIN_QUARTER, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SVS_SO_GAIN, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x4b), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_PI_CONTROLS, 0x81), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_FASTLOCK_COUNT_LOW, 0x80), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_MODE_00, 0x59), +}; + +static const struct qmp_phy_init_tbl sdm845_ufsphy_pcs_tbl[] = { + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RX_SIGDET_CTRL2, 0x6e), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TX_LARGE_AMP_DRV_LVL, 0x0a), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TX_SMALL_AMP_DRV_LVL, 0x02), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RX_SYM_RESYNC_CTRL, 0x03), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TX_MID_TERM_CTRL1, 0x43), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RX_SIGDET_CTRL1, 0x0f), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RX_MIN_HIBERN8_TIME, 0x9a), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_MULTI_LANE_CTRL1, 0x02), +}; /* struct qmp_phy_cfg - per-PHY initialization config */ struct qmp_phy_cfg { @@ -654,6 +736,9 @@ struct qmp_phy_cfg { /* Register offset of secondary tx/rx lanes for USB DP combo PHY */ unsigned int tx_b_lane_offset; unsigned int rx_b_lane_offset; + + /* true, if PCS block has no separate SW_RESET register */ + bool no_pcs_sw_reset; }; /** @@ -750,6 +835,10 @@ static const char * const qmp_v3_phy_clk_l[] = { "aux", "cfg_ahb", "ref", "com_aux", }; +static const char * const sdm845_ufs_phy_clk_l[] = { + "ref", "ref_aux", +}; + /* list of resets */ static const char * const msm8996_pciephy_reset_l[] = { "phy", "common", "cfg", @@ -919,6 +1008,35 @@ static const struct qmp_phy_cfg qmp_v3_usb3_uniphy_cfg = { .pwrdn_delay_max = POWER_DOWN_DELAY_US_MAX, }; +static const struct qmp_phy_cfg sdm845_ufsphy_cfg = { + .type = PHY_TYPE_UFS, + .nlanes = 2, + + .serdes_tbl = sdm845_ufsphy_serdes_tbl, + .serdes_tbl_num = ARRAY_SIZE(sdm845_ufsphy_serdes_tbl), + .tx_tbl = sdm845_ufsphy_tx_tbl, + .tx_tbl_num = ARRAY_SIZE(sdm845_ufsphy_tx_tbl), + .rx_tbl = sdm845_ufsphy_rx_tbl, + .rx_tbl_num = ARRAY_SIZE(sdm845_ufsphy_rx_tbl), + .pcs_tbl = sdm845_ufsphy_pcs_tbl, + .pcs_tbl_num = ARRAY_SIZE(sdm845_ufsphy_pcs_tbl), + .clk_list = sdm845_ufs_phy_clk_l, + .num_clks = ARRAY_SIZE(sdm845_ufs_phy_clk_l), + .vreg_list = qmp_phy_vreg_l, + .num_vregs = ARRAY_SIZE(qmp_phy_vreg_l), + .regs = sdm845_ufsphy_regs_layout, + + .start_ctrl = SERDES_START, + .pwrdn_ctrl = SW_PWRDN, + .mask_pcs_ready = PCS_READY, + + .is_dual_lane_phy = true, + .tx_b_lane_offset = 0x400, + .rx_b_lane_offset = 0x400, + + .no_pcs_sw_reset = true, +}; + static void qcom_qmp_phy_configure(void __iomem *base, const unsigned int *regs, const struct qmp_phy_init_tbl tbl[], @@ -1130,6 +1248,14 @@ static int qcom_qmp_phy_init(struct phy *phy) qcom_qmp_phy_configure(pcs, cfg->regs, cfg->pcs_tbl, cfg->pcs_tbl_num); + /* + * UFS PHY requires the deassert of software reset before serdes start. + * For UFS PHYs that do not have software reset control bits, defer + * starting serdes until the power on callback. + */ + if ((cfg->type == PHY_TYPE_UFS) && cfg->no_pcs_sw_reset) + goto out; + /* * Pull out PHY from POWER DOWN state. * This is active low enable signal to power-down PHY. @@ -1159,6 +1285,7 @@ static int qcom_qmp_phy_init(struct phy *phy) } qmp->phy_initialized = true; +out: return ret; err_pcs_ready: @@ -1181,7 +1308,8 @@ static int qcom_qmp_phy_exit(struct phy *phy) clk_disable_unprepare(qphy->pipe_clk); /* PHY reset */ - qphy_setbits(qphy->pcs, cfg->regs[QPHY_SW_RESET], SW_RESET); + if (!cfg->no_pcs_sw_reset) + qphy_setbits(qphy->pcs, cfg->regs[QPHY_SW_RESET], SW_RESET); /* stop SerDes and Phy-Coding-Sublayer */ qphy_clrbits(qphy->pcs, cfg->regs[QPHY_START_CTRL], cfg->start_ctrl); @@ -1199,6 +1327,44 @@ static int qcom_qmp_phy_exit(struct phy *phy) return 0; } +static int qcom_qmp_phy_poweron(struct phy *phy) +{ + struct qmp_phy *qphy = phy_get_drvdata(phy); + struct qcom_qmp *qmp = qphy->qmp; + const struct qmp_phy_cfg *cfg = qmp->cfg; + void __iomem *pcs = qphy->pcs; + void __iomem *status; + unsigned int mask, val; + int ret = 0; + + if (cfg->type != PHY_TYPE_UFS) + return 0; + + /* + * For UFS PHY that has not software reset control, serdes start + * should only happen when UFS driver explicitly calls phy_power_on + * after it deasserts software reset. + */ + if (cfg->no_pcs_sw_reset && !qmp->phy_initialized && + (qmp->init_count != 0)) { + /* start SerDes and Phy-Coding-Sublayer */ + qphy_setbits(pcs, cfg->regs[QPHY_START_CTRL], cfg->start_ctrl); + + status = pcs + cfg->regs[QPHY_PCS_READY_STATUS]; + mask = cfg->mask_pcs_ready; + + ret = readl_poll_timeout(status, val, !(val & mask), 1, + PHY_INIT_COMPLETE_TIMEOUT); + if (ret) { + dev_err(qmp->dev, "phy initialization timed-out\n"); + return ret; + } + qmp->phy_initialized = true; + } + + return ret; +} + static int qcom_qmp_phy_set_mode(struct phy *phy, enum phy_mode mode) { struct qmp_phy *qphy = phy_get_drvdata(phy); @@ -1428,6 +1594,7 @@ static int phy_pipe_clk_register(struct qcom_qmp *qmp, struct device_node *np) static const struct phy_ops qcom_qmp_phy_gen_ops = { .init = qcom_qmp_phy_init, .exit = qcom_qmp_phy_exit, + .power_on = qcom_qmp_phy_poweron, .set_mode = qcom_qmp_phy_set_mode, .owner = THIS_MODULE, }; @@ -1530,6 +1697,9 @@ static const struct of_device_id qcom_qmp_phy_of_match_table[] = { }, { .compatible = "qcom,sdm845-qmp-usb3-uni-phy", .data = &qmp_v3_usb3_uniphy_cfg, + }, { + .compatible = "qcom,sdm845-qmp-ufs-phy", + .data = &sdm845_ufsphy_cfg, }, { }, }; diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.h b/drivers/phy/qualcomm/phy-qcom-qmp.h index 5d78d43ba9fc5..d201cc307151c 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp.h +++ b/drivers/phy/qualcomm/phy-qcom-qmp.h @@ -184,6 +184,8 @@ #define QSERDES_V3_COM_VCO_TUNE2_MODE0 0x0f8 #define QSERDES_V3_COM_VCO_TUNE1_MODE1 0x0fc #define QSERDES_V3_COM_VCO_TUNE2_MODE1 0x100 +#define QSERDES_V3_COM_VCO_TUNE_INITVAL1 0x104 +#define QSERDES_V3_COM_VCO_TUNE_INITVAL2 0x108 #define QSERDES_V3_COM_VCO_TUNE_TIMER1 0x11c #define QSERDES_V3_COM_VCO_TUNE_TIMER2 0x120 #define QSERDES_V3_COM_CLK_SELECT 0x138 @@ -211,8 +213,13 @@ /* Only for QMP V3 PHY - RX registers */ #define QSERDES_V3_RX_UCDR_SO_GAIN_HALF 0x00c #define QSERDES_V3_RX_UCDR_SO_GAIN 0x014 +#define QSERDES_V3_RX_UCDR_SVS_SO_GAIN_HALF 0x024 +#define QSERDES_V3_RX_UCDR_SVS_SO_GAIN_QUARTER 0x028 +#define QSERDES_V3_RX_UCDR_SVS_SO_GAIN 0x02c #define QSERDES_V3_RX_UCDR_FASTLOCK_FO_GAIN 0x030 #define QSERDES_V3_RX_UCDR_SO_SATURATION_AND_ENABLE 0x034 +#define QSERDES_V3_RX_UCDR_FASTLOCK_COUNT_LOW 0x03c +#define QSERDES_V3_RX_UCDR_PI_CONTROLS 0x044 #define QSERDES_V3_RX_RX_TERM_BW 0x07c #define QSERDES_V3_RX_VGA_CAL_CNTRL1 0x0bc #define QSERDES_V3_RX_VGA_CAL_CNTRL2 0x0c0 @@ -239,6 +246,8 @@ #define QPHY_V3_PCS_TXMGN_V3 0x018 #define QPHY_V3_PCS_TXMGN_V4 0x01c #define QPHY_V3_PCS_TXMGN_LS 0x020 +#define QPHY_V3_PCS_TX_LARGE_AMP_DRV_LVL 0x02c +#define QPHY_V3_PCS_TX_SMALL_AMP_DRV_LVL 0x034 #define QPHY_V3_PCS_TXDEEMPH_M6DB_V0 0x024 #define QPHY_V3_PCS_TXDEEMPH_M3P5DB_V0 0x028 #define QPHY_V3_PCS_TXDEEMPH_M6DB_V1 0x02c @@ -275,6 +284,12 @@ #define QPHY_V3_PCS_FLL_CNT_VAL_L 0x0cc #define QPHY_V3_PCS_FLL_CNT_VAL_H_TOL 0x0d0 #define QPHY_V3_PCS_FLL_MAN_CODE 0x0d4 +#define QPHY_V3_PCS_RX_SYM_RESYNC_CTRL 0x134 +#define QPHY_V3_PCS_RX_MIN_HIBERN8_TIME 0x138 +#define QPHY_V3_PCS_RX_SIGDET_CTRL1 0x13c +#define QPHY_V3_PCS_RX_SIGDET_CTRL2 0x140 +#define QPHY_V3_PCS_TX_MID_TERM_CTRL1 0x1bc +#define QPHY_V3_PCS_MULTI_LANE_CTRL1 0x1c4 #define QPHY_V3_PCS_RX_SIGDET_LVL 0x1d8 #define QPHY_V3_PCS_REFGEN_REQ_CONFIG1 0x20c #define QPHY_V3_PCS_REFGEN_REQ_CONFIG2 0x210 From 99c7c7364b714e1de54a25c3642d991de1675e27 Mon Sep 17 00:00:00 2001 From: Can Guo Date: Tue, 25 Sep 2018 12:10:12 +0530 Subject: [PATCH 36/41] dt-bindings: phy-qcom-qmp: Add UFS phy compatible string for sdm845 Update the compatible string for UFS QMP PHY on SDM845. Signed-off-by: Can Guo Reviewed-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt b/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt index 2b161bf15d5d8..adf20b2bdf715 100644 --- a/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt +++ b/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt @@ -10,7 +10,8 @@ Required properties: "qcom,msm8996-qmp-pcie-phy" for 14nm PCIe phy on msm8996, "qcom,msm8996-qmp-usb3-phy" for 14nm USB3 phy on msm8996, "qcom,sdm845-qmp-usb3-phy" for USB3 QMP V3 phy on sdm845, - "qcom,sdm845-qmp-usb3-uni-phy" for USB3 QMP V3 UNI phy on sdm845. + "qcom,sdm845-qmp-usb3-uni-phy" for USB3 QMP V3 UNI phy on sdm845, + "qcom,sdm845-qmp-ufs-phy" for UFS QMP phy on sdm845. - reg: - index 0: address and length of register set for PHY's common @@ -38,6 +39,7 @@ Required properties: "aux" for phy aux clock, "ref" for 19.2 MHz ref clk, "com_aux" for phy common block aux clock, + "ref_aux" for phy reference aux clock, For "qcom,msm8996-qmp-pcie-phy" must contain: "aux", "cfg_ahb", "ref". For "qcom,msm8996-qmp-usb3-phy" must contain: From 2ba3c43f09c50eb1c0472decdfba71010d8694dc Mon Sep 17 00:00:00 2001 From: Vivek Gautam Date: Tue, 4 Sep 2018 15:47:17 +0530 Subject: [PATCH 37/41] phy: qcom-ufs: Remove stale methods that handle ref clk Remove ufs_qcom_phy_enable/(disable)_dev_ref_clk() that are not being used by any code. Signed-off-by: Vivek Gautam Reviewed-by: Bjorn Andersson Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/qualcomm/phy-qcom-ufs.c | 50 ----------------------------- include/linux/phy/phy-qcom-ufs.h | 14 -------- 2 files changed, 64 deletions(-) diff --git a/drivers/phy/qualcomm/phy-qcom-ufs.c b/drivers/phy/qualcomm/phy-qcom-ufs.c index c5493ea512828..f2979ccad00a3 100644 --- a/drivers/phy/qualcomm/phy-qcom-ufs.c +++ b/drivers/phy/qualcomm/phy-qcom-ufs.c @@ -431,56 +431,6 @@ static void ufs_qcom_phy_disable_ref_clk(struct ufs_qcom_phy *phy) } } -#define UFS_REF_CLK_EN (1 << 5) - -static void ufs_qcom_phy_dev_ref_clk_ctrl(struct phy *generic_phy, bool enable) -{ - struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy); - - if (phy->dev_ref_clk_ctrl_mmio && - (enable ^ phy->is_dev_ref_clk_enabled)) { - u32 temp = readl_relaxed(phy->dev_ref_clk_ctrl_mmio); - - if (enable) - temp |= UFS_REF_CLK_EN; - else - temp &= ~UFS_REF_CLK_EN; - - /* - * If we are here to disable this clock immediately after - * entering into hibern8, we need to make sure that device - * ref_clk is active atleast 1us after the hibern8 enter. - */ - if (!enable) - udelay(1); - - writel_relaxed(temp, phy->dev_ref_clk_ctrl_mmio); - /* ensure that ref_clk is enabled/disabled before we return */ - wmb(); - /* - * If we call hibern8 exit after this, we need to make sure that - * device ref_clk is stable for atleast 1us before the hibern8 - * exit command. - */ - if (enable) - udelay(1); - - phy->is_dev_ref_clk_enabled = enable; - } -} - -void ufs_qcom_phy_enable_dev_ref_clk(struct phy *generic_phy) -{ - ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, true); -} -EXPORT_SYMBOL_GPL(ufs_qcom_phy_enable_dev_ref_clk); - -void ufs_qcom_phy_disable_dev_ref_clk(struct phy *generic_phy) -{ - ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, false); -} -EXPORT_SYMBOL_GPL(ufs_qcom_phy_disable_dev_ref_clk); - /* Turn ON M-PHY RMMI interface clocks */ static int ufs_qcom_phy_enable_iface_clk(struct ufs_qcom_phy *phy) { diff --git a/include/linux/phy/phy-qcom-ufs.h b/include/linux/phy/phy-qcom-ufs.h index 0a2c18a9771da..9dd85071bccef 100644 --- a/include/linux/phy/phy-qcom-ufs.h +++ b/include/linux/phy/phy-qcom-ufs.h @@ -17,20 +17,6 @@ #include "phy.h" -/** - * ufs_qcom_phy_enable_dev_ref_clk() - Enable the device - * ref clock. - * @phy: reference to a generic phy. - */ -void ufs_qcom_phy_enable_dev_ref_clk(struct phy *phy); - -/** - * ufs_qcom_phy_disable_dev_ref_clk() - Disable the device - * ref clock. - * @phy: reference to a generic phy. - */ -void ufs_qcom_phy_disable_dev_ref_clk(struct phy *phy); - int ufs_qcom_phy_set_tx_lane_enable(struct phy *phy, u32 tx_lanes); void ufs_qcom_phy_save_controller_version(struct phy *phy, u8 major, u16 minor, u16 step); From 1e1e465c6d23aa7d1858eb2894408f15770af16c Mon Sep 17 00:00:00 2001 From: Vivek Gautam Date: Tue, 4 Sep 2018 15:47:18 +0530 Subject: [PATCH 38/41] scsi/ufs: qcom: Remove ufs_qcom_phy_*() calls from host The host makes direct calls into phy using ufs_qcom_phy_*() APIs. These APIs are only defined for 20nm qcom-ufs-qmp phy which is not being used by any architecture as yet. Future architectures too are not going to use 20nm ufs phy. So remove these ufs_qcom_phy_*() calls from host to let further change declare the 20nm phy as broken. Also remove couple of stale enum defines for ufs phy. Signed-off-by: Vivek Gautam Acked-by: Martin K. Petersen Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/qualcomm/phy-qcom-ufs-i.h | 2 +- drivers/scsi/ufs/ufs-qcom.c | 28 +-------------------------- drivers/scsi/ufs/ufs-qcom.h | 5 ----- include/linux/phy/phy-qcom-ufs.h | 24 ----------------------- 4 files changed, 2 insertions(+), 57 deletions(-) delete mode 100644 include/linux/phy/phy-qcom-ufs.h diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-i.h b/drivers/phy/qualcomm/phy-qcom-ufs-i.h index 822c83b8efcd1..681644e43248c 100644 --- a/drivers/phy/qualcomm/phy-qcom-ufs-i.h +++ b/drivers/phy/qualcomm/phy-qcom-ufs-i.h @@ -17,9 +17,9 @@ #include #include +#include #include #include -#include #include #include #include diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c index 75ee5906b9663..3dc4501c69457 100644 --- a/drivers/scsi/ufs/ufs-qcom.c +++ b/drivers/scsi/ufs/ufs-qcom.c @@ -16,7 +16,6 @@ #include #include #include -#include #include "ufshcd.h" #include "ufshcd-pltfrm.h" @@ -189,22 +188,9 @@ static int ufs_qcom_init_lane_clks(struct ufs_qcom_host *host) static int ufs_qcom_link_startup_post_change(struct ufs_hba *hba) { - struct ufs_qcom_host *host = ufshcd_get_variant(hba); - struct phy *phy = host->generic_phy; u32 tx_lanes; - int err = 0; - - err = ufs_qcom_get_connected_tx_lanes(hba, &tx_lanes); - if (err) - goto out; - err = ufs_qcom_phy_set_tx_lane_enable(phy, tx_lanes); - if (err) - dev_err(hba->dev, "%s: ufs_qcom_phy_set_tx_lane_enable failed\n", - __func__); - -out: - return err; + return ufs_qcom_get_connected_tx_lanes(hba, &tx_lanes); } static int ufs_qcom_check_hibern8(struct ufs_hba *hba) @@ -932,10 +918,8 @@ static int ufs_qcom_pwr_change_notify(struct ufs_hba *hba, { u32 val; struct ufs_qcom_host *host = ufshcd_get_variant(hba); - struct phy *phy = host->generic_phy; struct ufs_qcom_dev_params ufs_qcom_cap; int ret = 0; - int res = 0; if (!dev_req_params) { pr_err("%s: incoming dev_req_params is NULL\n", __func__); @@ -1002,12 +986,6 @@ static int ufs_qcom_pwr_change_notify(struct ufs_hba *hba, } val = ~(MAX_U32 << dev_req_params->lane_tx); - res = ufs_qcom_phy_set_tx_lane_enable(phy, val); - if (res) { - dev_err(hba->dev, "%s: ufs_qcom_phy_set_tx_lane_enable() failed res = %d\n", - __func__, res); - ret = res; - } /* cache the power mode parameters to use internally */ memcpy(&host->dev_req_params, @@ -1264,10 +1242,6 @@ static int ufs_qcom_init(struct ufs_hba *hba) } } - /* update phy revision information before calling phy_init() */ - ufs_qcom_phy_save_controller_version(host->generic_phy, - host->hw_ver.major, host->hw_ver.minor, host->hw_ver.step); - err = ufs_qcom_init_lane_clks(host); if (err) goto out_variant_clear; diff --git a/drivers/scsi/ufs/ufs-qcom.h b/drivers/scsi/ufs/ufs-qcom.h index 295f4bef6a0e9..c114826316eb0 100644 --- a/drivers/scsi/ufs/ufs-qcom.h +++ b/drivers/scsi/ufs/ufs-qcom.h @@ -129,11 +129,6 @@ enum { MASK_CLK_NS_REG = 0xFFFC00, }; -enum ufs_qcom_phy_init_type { - UFS_PHY_INIT_FULL, - UFS_PHY_INIT_CFG_RESTORE, -}; - /* QCOM UFS debug print bit mask */ #define UFS_QCOM_DBG_PRINT_REGS_EN BIT(0) #define UFS_QCOM_DBG_PRINT_ICE_REGS_EN BIT(1) diff --git a/include/linux/phy/phy-qcom-ufs.h b/include/linux/phy/phy-qcom-ufs.h deleted file mode 100644 index 9dd85071bccef..0000000000000 --- a/include/linux/phy/phy-qcom-ufs.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2013-2015, Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * 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. - * - */ - -#ifndef PHY_QCOM_UFS_H_ -#define PHY_QCOM_UFS_H_ - -#include "phy.h" - -int ufs_qcom_phy_set_tx_lane_enable(struct phy *phy, u32 tx_lanes); -void ufs_qcom_phy_save_controller_version(struct phy *phy, - u8 major, u16 minor, u16 step); - -#endif /* PHY_QCOM_UFS_H_ */ From 82af0932486716bb870c3c0fd1d8c65b3062956f Mon Sep 17 00:00:00 2001 From: Vivek Gautam Date: Tue, 4 Sep 2018 15:47:19 +0530 Subject: [PATCH 39/41] phy: qcom-ufs: Declare 20nm qcom ufs qmp phy as Broken Fork out separate configs for 14nm and 20nm qcom ufs qmp phys to declare the 20nm phy as broken. Signed-off-by: Vivek Gautam Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/qualcomm/Kconfig | 17 +++++++++++++++++ drivers/phy/qualcomm/Makefile | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig index 632a0e73ee100..32f7d34eb7840 100644 --- a/drivers/phy/qualcomm/Kconfig +++ b/drivers/phy/qualcomm/Kconfig @@ -50,6 +50,23 @@ config PHY_QCOM_UFS help Support for UFS PHY on QCOM chipsets. +if PHY_QCOM_UFS + +config PHY_QCOM_UFS_14NM + tristate + default PHY_QCOM_UFS + help + Support for 14nm UFS QMP phy present on QCOM chipsets. + +config PHY_QCOM_UFS_20NM + tristate + default PHY_QCOM_UFS + depends on BROKEN + help + Support for 20nm UFS QMP phy present on QCOM chipsets. + +endif + config PHY_QCOM_USB_HS tristate "Qualcomm USB HS PHY module" depends on USB_ULPI_BUS diff --git a/drivers/phy/qualcomm/Makefile b/drivers/phy/qualcomm/Makefile index deb831f453ae3..c56efd3af2055 100644 --- a/drivers/phy/qualcomm/Makefile +++ b/drivers/phy/qualcomm/Makefile @@ -5,7 +5,7 @@ obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o obj-$(CONFIG_PHY_QCOM_QMP) += phy-qcom-qmp.o obj-$(CONFIG_PHY_QCOM_QUSB2) += phy-qcom-qusb2.o obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs.o -obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-14nm.o -obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-20nm.o +obj-$(CONFIG_PHY_QCOM_UFS_14NM) += phy-qcom-ufs-qmp-14nm.o +obj-$(CONFIG_PHY_QCOM_UFS_20NM) += phy-qcom-ufs-qmp-20nm.o obj-$(CONFIG_PHY_QCOM_USB_HS) += phy-qcom-usb-hs.o obj-$(CONFIG_PHY_QCOM_USB_HSIC) += phy-qcom-usb-hsic.o From 3a00dae006623d799266d85f28b5f76ef07d6b6c Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Tue, 25 Sep 2018 21:54:07 +0200 Subject: [PATCH 40/41] phy: lantiq: Fix compile warning This local variable is unused, remove it. Fixes: dea54fbad332 ("phy: Add an USB PHY driver for the Lantiq SoCs using the RCU module") Signed-off-by: Hauke Mehrtens Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/lantiq/phy-lantiq-rcu-usb2.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/phy/lantiq/phy-lantiq-rcu-usb2.c b/drivers/phy/lantiq/phy-lantiq-rcu-usb2.c index a918c5b2c4d9d..f9e0dd19ff262 100644 --- a/drivers/phy/lantiq/phy-lantiq-rcu-usb2.c +++ b/drivers/phy/lantiq/phy-lantiq-rcu-usb2.c @@ -156,7 +156,6 @@ static int ltq_rcu_usb2_of_parse(struct ltq_rcu_usb2_priv *priv, { struct device *dev = priv->dev; const __be32 *offset; - int ret; priv->reg_bits = of_device_get_match_data(dev); From 566b388440bb61447df7cdf5f4508a0fce493e00 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 26 Sep 2018 01:52:21 +0000 Subject: [PATCH 41/41] phy: renesas: convert to SPDX identifiers This patch updates license to use SPDX-License-Identifier instead of verbose license text. Signed-off-by: Kuninori Morimoto Reviewed-by: Simon Horman Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/renesas/Kconfig | 1 + drivers/phy/renesas/Makefile | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/phy/renesas/Kconfig b/drivers/phy/renesas/Kconfig index 4bd390c79d214..e340a925bbb1b 100644 --- a/drivers/phy/renesas/Kconfig +++ b/drivers/phy/renesas/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 # # Phy drivers for Renesas platforms # diff --git a/drivers/phy/renesas/Makefile b/drivers/phy/renesas/Makefile index 4b76fc439ed66..b599ff8a4349c 100644 --- a/drivers/phy/renesas/Makefile +++ b/drivers/phy/renesas/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_PHY_RCAR_GEN2) += phy-rcar-gen2.o obj-$(CONFIG_PHY_RCAR_GEN3_PCIE) += phy-rcar-gen3-pcie.o obj-$(CONFIG_PHY_RCAR_GEN3_USB2) += phy-rcar-gen3-usb2.o