diff options
Diffstat (limited to 'drivers/phy')
-rw-r--r-- | drivers/phy/mediatek/Kconfig | 9 | ||||
-rw-r--r-- | drivers/phy/mediatek/Makefile | 1 | ||||
-rw-r--r-- | drivers/phy/mediatek/phy-mtk-xsphy.c | 600 | ||||
-rw-r--r-- | drivers/phy/motorola/phy-mapphone-mdm6600.c | 85 | ||||
-rw-r--r-- | drivers/phy/phy-core.c | 18 | ||||
-rw-r--r-- | drivers/phy/qualcomm/Kconfig | 11 | ||||
-rw-r--r-- | drivers/phy/qualcomm/Makefile | 1 | ||||
-rw-r--r-- | drivers/phy/qualcomm/phy-ath79-usb.c | 108 | ||||
-rw-r--r-- | drivers/phy/qualcomm/phy-qcom-qmp.c | 169 | ||||
-rw-r--r-- | drivers/phy/qualcomm/phy-qcom-qmp.h | 5 | ||||
-rw-r--r-- | drivers/phy/qualcomm/phy-qcom-qusb2.c | 130 | ||||
-rw-r--r-- | drivers/phy/samsung/phy-exynos-mipi-video.c | 34 | ||||
-rw-r--r-- | drivers/phy/st/phy-stm32-usbphyc.c | 9 | ||||
-rw-r--r-- | drivers/phy/tegra/xusb.c | 15 |
14 files changed, 1132 insertions, 63 deletions
diff --git a/drivers/phy/mediatek/Kconfig b/drivers/phy/mediatek/Kconfig index 88ab4e25e34f..8857d00b3c65 100644 --- a/drivers/phy/mediatek/Kconfig +++ b/drivers/phy/mediatek/Kconfig @@ -12,3 +12,12 @@ config PHY_MTK_TPHY different banks layout, the T-PHY with shared banks between multi-ports is first version, otherwise is second veriosn, so you can easily distinguish them by banks layout. + +config PHY_MTK_XSPHY + tristate "MediaTek XS-PHY Driver" + depends on ARCH_MEDIATEK && OF + select GENERIC_PHY + help + Enable this to support the SuperSpeedPlus XS-PHY transceiver for + USB3.1 GEN2 controllers on MediaTek chips. The driver supports + multiple USB2.0, USB3.1 GEN2 ports. diff --git a/drivers/phy/mediatek/Makefile b/drivers/phy/mediatek/Makefile index 763a92eefa00..e5074b607d3d 100644 --- a/drivers/phy/mediatek/Makefile +++ b/drivers/phy/mediatek/Makefile @@ -3,3 +3,4 @@ # obj-$(CONFIG_PHY_MTK_TPHY) += phy-mtk-tphy.o +obj-$(CONFIG_PHY_MTK_XSPHY) += phy-mtk-xsphy.o diff --git a/drivers/phy/mediatek/phy-mtk-xsphy.c b/drivers/phy/mediatek/phy-mtk-xsphy.c new file mode 100644 index 000000000000..020cd0227397 --- /dev/null +++ b/drivers/phy/mediatek/phy-mtk-xsphy.c @@ -0,0 +1,600 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MediaTek USB3.1 gen2 xsphy Driver + * + * Copyright (c) 2018 MediaTek Inc. + * Author: Chunfeng Yun <chunfeng.yun@mediatek.com> + * + */ + +#include <dt-bindings/phy/phy.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> + +/* u2 phy banks */ +#define SSUSB_SIFSLV_MISC 0x000 +#define SSUSB_SIFSLV_U2FREQ 0x100 +#define SSUSB_SIFSLV_U2PHY_COM 0x300 + +/* u3 phy shared banks */ +#define SSPXTP_SIFSLV_DIG_GLB 0x000 +#define SSPXTP_SIFSLV_PHYA_GLB 0x100 + +/* u3 phy banks */ +#define SSPXTP_SIFSLV_DIG_LN_TOP 0x000 +#define SSPXTP_SIFSLV_DIG_LN_TX0 0x100 +#define SSPXTP_SIFSLV_DIG_LN_RX0 0x200 +#define SSPXTP_SIFSLV_DIG_LN_DAIF 0x300 +#define SSPXTP_SIFSLV_PHYA_LN 0x400 + +#define XSP_U2FREQ_FMCR0 ((SSUSB_SIFSLV_U2FREQ) + 0x00) +#define P2F_RG_FREQDET_EN BIT(24) +#define P2F_RG_CYCLECNT GENMASK(23, 0) +#define P2F_RG_CYCLECNT_VAL(x) ((P2F_RG_CYCLECNT) & (x)) + +#define XSP_U2FREQ_MMONR0 ((SSUSB_SIFSLV_U2FREQ) + 0x0c) + +#define XSP_U2FREQ_FMMONR1 ((SSUSB_SIFSLV_U2FREQ) + 0x10) +#define P2F_RG_FRCK_EN BIT(8) +#define P2F_USB_FM_VALID BIT(0) + +#define XSP_USBPHYACR0 ((SSUSB_SIFSLV_U2PHY_COM) + 0x00) +#define P2A0_RG_INTR_EN BIT(5) + +#define XSP_USBPHYACR1 ((SSUSB_SIFSLV_U2PHY_COM) + 0x04) +#define P2A1_RG_INTR_CAL GENMASK(23, 19) +#define P2A1_RG_INTR_CAL_VAL(x) ((0x1f & (x)) << 19) +#define P2A1_RG_VRT_SEL GENMASK(14, 12) +#define P2A1_RG_VRT_SEL_VAL(x) ((0x7 & (x)) << 12) +#define P2A1_RG_TERM_SEL GENMASK(10, 8) +#define P2A1_RG_TERM_SEL_VAL(x) ((0x7 & (x)) << 8) + +#define XSP_USBPHYACR5 ((SSUSB_SIFSLV_U2PHY_COM) + 0x014) +#define P2A5_RG_HSTX_SRCAL_EN BIT(15) +#define P2A5_RG_HSTX_SRCTRL GENMASK(14, 12) +#define P2A5_RG_HSTX_SRCTRL_VAL(x) ((0x7 & (x)) << 12) + +#define XSP_USBPHYACR6 ((SSUSB_SIFSLV_U2PHY_COM) + 0x018) +#define P2A6_RG_BC11_SW_EN BIT(23) +#define P2A6_RG_OTG_VBUSCMP_EN BIT(20) + +#define XSP_U2PHYDTM1 ((SSUSB_SIFSLV_U2PHY_COM) + 0x06C) +#define P2D_FORCE_IDDIG BIT(9) +#define P2D_RG_VBUSVALID BIT(5) +#define P2D_RG_SESSEND BIT(4) +#define P2D_RG_AVALID BIT(2) +#define P2D_RG_IDDIG BIT(1) + +#define SSPXTP_PHYA_GLB_00 ((SSPXTP_SIFSLV_PHYA_GLB) + 0x00) +#define RG_XTP_GLB_BIAS_INTR_CTRL GENMASK(21, 16) +#define RG_XTP_GLB_BIAS_INTR_CTRL_VAL(x) ((0x3f & (x)) << 16) + +#define SSPXTP_PHYA_LN_04 ((SSPXTP_SIFSLV_PHYA_LN) + 0x04) +#define RG_XTP_LN0_TX_IMPSEL GENMASK(4, 0) +#define RG_XTP_LN0_TX_IMPSEL_VAL(x) (0x1f & (x)) + +#define SSPXTP_PHYA_LN_14 ((SSPXTP_SIFSLV_PHYA_LN) + 0x014) +#define RG_XTP_LN0_RX_IMPSEL GENMASK(4, 0) +#define RG_XTP_LN0_RX_IMPSEL_VAL(x) (0x1f & (x)) + +#define XSP_REF_CLK 26 /* MHZ */ +#define XSP_SLEW_RATE_COEF 17 +#define XSP_SR_COEF_DIVISOR 1000 +#define XSP_FM_DET_CYCLE_CNT 1024 + +struct xsphy_instance { + struct phy *phy; + void __iomem *port_base; + struct clk *ref_clk; /* reference clock of anolog phy */ + u32 index; + u32 type; + /* only for HQA test */ + int efuse_intr; + int efuse_tx_imp; + int efuse_rx_imp; + /* u2 eye diagram */ + int eye_src; + int eye_vrt; + int eye_term; +}; + +struct mtk_xsphy { + struct device *dev; + void __iomem *glb_base; /* only shared u3 sif */ + struct xsphy_instance **phys; + int nphys; + int src_ref_clk; /* MHZ, reference clock for slew rate calibrate */ + int src_coef; /* coefficient for slew rate calibrate */ +}; + +static void u2_phy_slew_rate_calibrate(struct mtk_xsphy *xsphy, + struct xsphy_instance *inst) +{ + void __iomem *pbase = inst->port_base; + int calib_val; + int fm_out; + u32 tmp; + + /* use force value */ + if (inst->eye_src) + return; + + /* enable USB ring oscillator */ + tmp = readl(pbase + XSP_USBPHYACR5); + tmp |= P2A5_RG_HSTX_SRCAL_EN; + writel(tmp, pbase + XSP_USBPHYACR5); + udelay(1); /* wait clock stable */ + + /* enable free run clock */ + tmp = readl(pbase + XSP_U2FREQ_FMMONR1); + tmp |= P2F_RG_FRCK_EN; + writel(tmp, pbase + XSP_U2FREQ_FMMONR1); + + /* set cycle count as 1024 */ + tmp = readl(pbase + XSP_U2FREQ_FMCR0); + tmp &= ~(P2F_RG_CYCLECNT); + tmp |= P2F_RG_CYCLECNT_VAL(XSP_FM_DET_CYCLE_CNT); + writel(tmp, pbase + XSP_U2FREQ_FMCR0); + + /* enable frequency meter */ + tmp = readl(pbase + XSP_U2FREQ_FMCR0); + tmp |= P2F_RG_FREQDET_EN; + writel(tmp, pbase + XSP_U2FREQ_FMCR0); + + /* ignore return value */ + readl_poll_timeout(pbase + XSP_U2FREQ_FMMONR1, tmp, + (tmp & P2F_USB_FM_VALID), 10, 200); + + fm_out = readl(pbase + XSP_U2FREQ_MMONR0); + + /* disable frequency meter */ + tmp = readl(pbase + XSP_U2FREQ_FMCR0); + tmp &= ~P2F_RG_FREQDET_EN; + writel(tmp, pbase + XSP_U2FREQ_FMCR0); + + /* disable free run clock */ + tmp = readl(pbase + XSP_U2FREQ_FMMONR1); + tmp &= ~P2F_RG_FRCK_EN; + writel(tmp, pbase + XSP_U2FREQ_FMMONR1); + + if (fm_out) { + /* (1024 / FM_OUT) x reference clock frequency x coefficient */ + tmp = xsphy->src_ref_clk * xsphy->src_coef; + tmp = (tmp * XSP_FM_DET_CYCLE_CNT) / fm_out; + calib_val = DIV_ROUND_CLOSEST(tmp, XSP_SR_COEF_DIVISOR); + } else { + /* if FM detection fail, set default value */ + calib_val = 3; + } + dev_dbg(xsphy->dev, "phy.%d, fm_out:%d, calib:%d (clk:%d, coef:%d)\n", + inst->index, fm_out, calib_val, + xsphy->src_ref_clk, xsphy->src_coef); + + /* set HS slew rate */ + tmp = readl(pbase + XSP_USBPHYACR5); + tmp &= ~P2A5_RG_HSTX_SRCTRL; + tmp |= P2A5_RG_HSTX_SRCTRL_VAL(calib_val); + writel(tmp, pbase + XSP_USBPHYACR5); + + /* disable USB ring oscillator */ + tmp = readl(pbase + XSP_USBPHYACR5); + tmp &= ~P2A5_RG_HSTX_SRCAL_EN; + writel(tmp, pbase + XSP_USBPHYACR5); +} + +static void u2_phy_instance_init(struct mtk_xsphy *xsphy, + struct xsphy_instance *inst) +{ + void __iomem *pbase = inst->port_base; + u32 tmp; + + /* DP/DM BC1.1 path Disable */ + tmp = readl(pbase + XSP_USBPHYACR6); + tmp &= ~P2A6_RG_BC11_SW_EN; + writel(tmp, pbase + XSP_USBPHYACR6); + + tmp = readl(pbase + XSP_USBPHYACR0); + tmp |= P2A0_RG_INTR_EN; + writel(tmp, pbase + XSP_USBPHYACR0); +} + +static void u2_phy_instance_power_on(struct mtk_xsphy *xsphy, + struct xsphy_instance *inst) +{ + void __iomem *pbase = inst->port_base; + u32 index = inst->index; + u32 tmp; + + tmp = readl(pbase + XSP_USBPHYACR6); + tmp |= P2A6_RG_OTG_VBUSCMP_EN; + writel(tmp, pbase + XSP_USBPHYACR6); + + tmp = readl(pbase + XSP_U2PHYDTM1); + tmp |= P2D_RG_VBUSVALID | P2D_RG_AVALID; + tmp &= ~P2D_RG_SESSEND; + writel(tmp, pbase + XSP_U2PHYDTM1); + + dev_dbg(xsphy->dev, "%s(%d)\n", __func__, index); +} + +static void u2_phy_instance_power_off(struct mtk_xsphy *xsphy, + struct xsphy_instance *inst) +{ + void __iomem *pbase = inst->port_base; + u32 index = inst->index; + u32 tmp; + + tmp = readl(pbase + XSP_USBPHYACR6); + tmp &= ~P2A6_RG_OTG_VBUSCMP_EN; + writel(tmp, pbase + XSP_USBPHYACR6); + + tmp = readl(pbase + XSP_U2PHYDTM1); + tmp &= ~(P2D_RG_VBUSVALID | P2D_RG_AVALID); + tmp |= P2D_RG_SESSEND; + writel(tmp, pbase + XSP_U2PHYDTM1); + + dev_dbg(xsphy->dev, "%s(%d)\n", __func__, index); +} + +static void u2_phy_instance_set_mode(struct mtk_xsphy *xsphy, + struct xsphy_instance *inst, + enum phy_mode mode) +{ + u32 tmp; + + tmp = readl(inst->port_base + XSP_U2PHYDTM1); + switch (mode) { + case PHY_MODE_USB_DEVICE: + tmp |= P2D_FORCE_IDDIG | P2D_RG_IDDIG; + break; + case PHY_MODE_USB_HOST: + tmp |= P2D_FORCE_IDDIG; + tmp &= ~P2D_RG_IDDIG; + break; + case PHY_MODE_USB_OTG: + tmp &= ~(P2D_FORCE_IDDIG | P2D_RG_IDDIG); + break; + default: + return; + } + writel(tmp, inst->port_base + XSP_U2PHYDTM1); +} + +static void phy_parse_property(struct mtk_xsphy *xsphy, + struct xsphy_instance *inst) +{ + struct device *dev = &inst->phy->dev; + + switch (inst->type) { + case PHY_TYPE_USB2: + device_property_read_u32(dev, "mediatek,efuse-intr", + &inst->efuse_intr); + device_property_read_u32(dev, "mediatek,eye-src", + &inst->eye_src); + device_property_read_u32(dev, "mediatek,eye-vrt", + &inst->eye_vrt); + device_property_read_u32(dev, "mediatek,eye-term", + &inst->eye_term); + dev_dbg(dev, "intr:%d, src:%d, vrt:%d, term:%d\n", + inst->efuse_intr, inst->eye_src, + inst->eye_vrt, inst->eye_term); + break; + case PHY_TYPE_USB3: + device_property_read_u32(dev, "mediatek,efuse-intr", + &inst->efuse_intr); + device_property_read_u32(dev, "mediatek,efuse-tx-imp", + &inst->efuse_tx_imp); + device_property_read_u32(dev, "mediatek,efuse-rx-imp", + &inst->efuse_rx_imp); + dev_dbg(dev, "intr:%d, tx-imp:%d, rx-imp:%d\n", + inst->efuse_intr, inst->efuse_tx_imp, + inst->efuse_rx_imp); + break; + default: + dev_err(xsphy->dev, "incompatible phy type\n"); + return; + } +} + +static void u2_phy_props_set(struct mtk_xsphy *xsphy, + struct xsphy_instance *inst) +{ + void __iomem *pbase = inst->port_base; + u32 tmp; + + if (inst->efuse_intr) { + tmp = readl(pbase + XSP_USBPHYACR1); + tmp &= ~P2A1_RG_INTR_CAL; + tmp |= P2A1_RG_INTR_CAL_VAL(inst->efuse_intr); + writel(tmp, pbase + XSP_USBPHYACR1); + } + + if (inst->eye_src) { + tmp = readl(pbase + XSP_USBPHYACR5); + tmp &= ~P2A5_RG_HSTX_SRCTRL; + tmp |= P2A5_RG_HSTX_SRCTRL_VAL(inst->eye_src); + writel(tmp, pbase + XSP_USBPHYACR5); + } + + if (inst->eye_vrt) { + tmp = readl(pbase + XSP_USBPHYACR1); + tmp &= ~P2A1_RG_VRT_SEL; + tmp |= P2A1_RG_VRT_SEL_VAL(inst->eye_vrt); + writel(tmp, pbase + XSP_USBPHYACR1); + } + + if (inst->eye_term) { + tmp = readl(pbase + XSP_USBPHYACR1); + tmp &= ~P2A1_RG_TERM_SEL; + tmp |= P2A1_RG_TERM_SEL_VAL(inst->eye_term); + writel(tmp, pbase + XSP_USBPHYACR1); + } +} + +static void u3_phy_props_set(struct mtk_xsphy *xsphy, + struct xsphy_instance *inst) +{ + void __iomem *pbase = inst->port_base; + u32 tmp; + + if (inst->efuse_intr) { + tmp = readl(xsphy->glb_base + SSPXTP_PHYA_GLB_00); + tmp &= ~RG_XTP_GLB_BIAS_INTR_CTRL; + tmp |= RG_XTP_GLB_BIAS_INTR_CTRL_VAL(inst->efuse_intr); + writel(tmp, xsphy->glb_base + SSPXTP_PHYA_GLB_00); + } + + if (inst->efuse_tx_imp) { + tmp = readl(pbase + SSPXTP_PHYA_LN_04); + tmp &= ~RG_XTP_LN0_TX_IMPSEL; + tmp |= RG_XTP_LN0_TX_IMPSEL_VAL(inst->efuse_tx_imp); + writel(tmp, pbase + SSPXTP_PHYA_LN_04); + } + + if (inst->efuse_rx_imp) { + tmp = readl(pbase + SSPXTP_PHYA_LN_14); + tmp &= ~RG_XTP_LN0_RX_IMPSEL; + tmp |= RG_XTP_LN0_RX_IMPSEL_VAL(inst->efuse_rx_imp); + writel(tmp, pbase + SSPXTP_PHYA_LN_14); + } +} + +static int mtk_phy_init(struct phy *phy) +{ + struct xsphy_instance *inst = phy_get_drvdata(phy); + struct mtk_xsphy *xsphy = dev_get_drvdata(phy->dev.parent); + int ret; + + ret = clk_prepare_enable(inst->ref_clk); + if (ret) { + dev_err(xsphy->dev, "failed to enable ref_clk\n"); + return ret; + } + + switch (inst->type) { + case PHY_TYPE_USB2: + u2_phy_instance_init(xsphy, inst); + u2_phy_props_set(xsphy, inst); + break; + case PHY_TYPE_USB3: + u3_phy_props_set(xsphy, inst); + break; + default: + dev_err(xsphy->dev, "incompatible phy type\n"); + clk_disable_unprepare(inst->ref_clk); + return -EINVAL; + } + + return 0; +} + +static int mtk_phy_power_on(struct phy *phy) +{ + struct xsphy_instance *inst = phy_get_drvdata(phy); + struct mtk_xsphy *xsphy = dev_get_drvdata(phy->dev.parent); + + if (inst->type == PHY_TYPE_USB2) { + u2_phy_instance_power_on(xsphy, inst); + u2_phy_slew_rate_calibrate(xsphy, inst); + } + + return 0; +} + +static int mtk_phy_power_off(struct phy *phy) +{ + struct xsphy_instance *inst = phy_get_drvdata(phy); + struct mtk_xsphy *xsphy = dev_get_drvdata(phy->dev.parent); + + if (inst->type == PHY_TYPE_USB2) + u2_phy_instance_power_off(xsphy, inst); + + return 0; +} + +static int mtk_phy_exit(struct phy *phy) +{ + struct xsphy_instance *inst = phy_get_drvdata(phy); + + clk_disable_unprepare(inst->ref_clk); + return 0; +} + +static int mtk_phy_set_mode(struct phy *phy, enum phy_mode mode) +{ + struct xsphy_instance *inst = phy_get_drvdata(phy); + struct mtk_xsphy *xsphy = dev_get_drvdata(phy->dev.parent); + + if (inst->type == PHY_TYPE_USB2) + u2_phy_instance_set_mode(xsphy, inst, mode); + + return 0; +} + +static struct phy *mtk_phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct mtk_xsphy *xsphy = dev_get_drvdata(dev); + struct xsphy_instance *inst = NULL; + struct device_node *phy_np = args->np; + int index; + + if (args->args_count != 1) { + dev_err(dev, "invalid number of cells in 'phy' property\n"); + return ERR_PTR(-EINVAL); + } + + for (index = 0; index < xsphy->nphys; index++) + if (phy_np == xsphy->phys[index]->phy->dev.of_node) { + inst = xsphy->phys[index]; + break; + } + + if (!inst) { + dev_err(dev, "failed to find appropriate phy\n"); + return ERR_PTR(-EINVAL); + } + + inst->type = args->args[0]; + if (!(inst->type == PHY_TYPE_USB2 || + inst->type == PHY_TYPE_USB3)) { + dev_err(dev, "unsupported phy type: %d\n", inst->type); + return ERR_PTR(-EINVAL); + } + + phy_parse_property(xsphy, inst); + + return inst->phy; +} + +static const struct phy_ops mtk_xsphy_ops = { + .init = mtk_phy_init, + .exit = mtk_phy_exit, + .power_on = mtk_phy_power_on, + .power_off = mtk_phy_power_off, + .set_mode = mtk_phy_set_mode, + .owner = THIS_MODULE, +}; + +static const struct of_device_id mtk_xsphy_id_table[] = { + { .compatible = "mediatek,xsphy", }, + { }, +}; +MODULE_DEVICE_TABLE(of, mtk_xsphy_id_table); + +static int mtk_xsphy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *child_np; + struct phy_provider *provider; + struct resource *glb_res; + struct mtk_xsphy *xsphy; + struct resource res; + int port, retval; + + xsphy = devm_kzalloc(dev, sizeof(*xsphy), GFP_KERNEL); + if (!xsphy) + return -ENOMEM; + + xsphy->nphys = of_get_child_count(np); + xsphy->phys = devm_kcalloc(dev, xsphy->nphys, + sizeof(*xsphy->phys), GFP_KERNEL); + if (!xsphy->phys) + return -ENOMEM; + + xsphy->dev = dev; + platform_set_drvdata(pdev, xsphy); + + glb_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + /* optional, may not exist if no u3 phys */ + if (glb_res) { + /* get banks shared by multiple u3 phys */ + xsphy->glb_base = devm_ioremap_resource(dev, glb_res); + if (IS_ERR(xsphy->glb_base)) { + dev_err(dev, "failed to remap glb regs\n"); + return PTR_ERR(xsphy->glb_base); + } + } + + xsphy->src_ref_clk = XSP_REF_CLK; + xsphy->src_coef = XSP_SLEW_RATE_COEF; + /* update parameters of slew rate calibrate if exist */ + device_property_read_u32(dev, "mediatek,src-ref-clk-mhz", + &xsphy->src_ref_clk); + device_property_read_u32(dev, "mediatek,src-coef", &xsphy->src_coef); + + port = 0; + for_each_child_of_node(np, child_np) { + struct xsphy_instance *inst; + struct phy *phy; + + inst = devm_kzalloc(dev, sizeof(*inst), GFP_KERNEL); + if (!inst) { + retval = -ENOMEM; + goto put_child; + } + + xsphy->phys[port] = inst; + + phy = devm_phy_create(dev, child_np, &mtk_xsphy_ops); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create phy\n"); + retval = PTR_ERR(phy); + goto put_child; + } + + retval = of_address_to_resource(child_np, 0, &res); + if (retval) { + dev_err(dev, "failed to get address resource(id-%d)\n", + port); + goto put_child; + } + + inst->port_base = devm_ioremap_resource(&phy->dev, &res); + if (IS_ERR(inst->port_base)) { + dev_err(dev, "failed to remap phy regs\n"); + retval = PTR_ERR(inst->port_base); + goto put_child; + } + + inst->phy = phy; + inst->index = port; + phy_set_drvdata(phy, inst); + port++; + + inst->ref_clk = devm_clk_get(&phy->dev, "ref"); + if (IS_ERR(inst->ref_clk)) { + dev_err(dev, "failed to get ref_clk(id-%d)\n", port); + retval = PTR_ERR(inst->ref_clk); + goto put_child; + } + } + + provider = devm_of_phy_provider_register(dev, mtk_phy_xlate); + return PTR_ERR_OR_ZERO(provider); + +put_child: + of_node_put(child_np); + return retval; +} + +static struct platform_driver mtk_xsphy_driver = { + .probe = mtk_xsphy_probe, + .driver = { + .name = "mtk-xsphy", + .of_match_table = mtk_xsphy_id_table, + }, +}; + +module_platform_driver(mtk_xsphy_driver); + +MODULE_AUTHOR("Chunfeng Yun <chunfeng.yun@mediatek.com>"); +MODULE_DESCRIPTION("MediaTek USB XS-PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/motorola/phy-mapphone-mdm6600.c b/drivers/phy/motorola/phy-mapphone-mdm6600.c index 5439dd90d0dd..23705e1a0023 100644 --- a/drivers/phy/motorola/phy-mapphone-mdm6600.c +++ b/drivers/phy/motorola/phy-mapphone-mdm6600.c @@ -19,6 +19,8 @@ #define PHY_MDM6600_PHY_DELAY_MS 4000 /* PHY enable 2.2s to 3.5s */ #define PHY_MDM6600_ENABLED_DELAY_MS 8000 /* 8s more total for MDM6600 */ +#define MDM6600_MODEM_IDLE_DELAY_MS 1000 /* modem after USB suspend */ +#define MDM6600_MODEM_WAKE_DELAY_MS 200 /* modem response after idle */ enum phy_mdm6600_ctrl_lines { PHY_MDM6600_ENABLE, /* USB PHY enable */ @@ -93,9 +95,11 @@ struct phy_mdm6600 { struct gpio_descs *cmd_gpios; struct delayed_work bootup_work; struct delayed_work status_work; + struct delayed_work modem_wake_work; struct completion ack; bool enabled; /* mdm6600 phy enabled */ bool running; /* mdm6600 boot done */ + bool awake; /* mdm6600 respnds on n_gsm */ int status; }; @@ -446,6 +450,62 @@ static void phy_mdm6600_deferred_power_on(struct work_struct *work) dev_err(ddata->dev, "Device not functional\n"); } +/* + * USB suspend puts mdm6600 into low power mode. For any n_gsm using apps, + * we need to keep the modem awake by kicking it's mode0 GPIO. This will + * keep the modem awake for about 1.2 seconds. When no n_gsm apps are using + * the modem, runtime PM auto mode can be enabled so modem can enter low + * power mode. + */ +static void phy_mdm6600_wake_modem(struct phy_mdm6600 *ddata) +{ + struct gpio_desc *mode_gpio0; + + mode_gpio0 = ddata->mode_gpios->desc[PHY_MDM6600_MODE0]; + gpiod_set_value_cansleep(mode_gpio0, 1); + usleep_range(5, 15); + gpiod_set_value_cansleep(mode_gpio0, 0); + if (ddata->awake) + usleep_range(5, 15); + else + msleep(MDM6600_MODEM_WAKE_DELAY_MS); +} + +static void phy_mdm6600_modem_wake(struct work_struct *work) +{ + struct phy_mdm6600 *ddata; + + ddata = container_of(work, struct phy_mdm6600, modem_wake_work.work); + phy_mdm6600_wake_modem(ddata); + schedule_delayed_work(&ddata->modem_wake_work, + msecs_to_jiffies(MDM6600_MODEM_IDLE_DELAY_MS)); +} + +static int __maybe_unused phy_mdm6600_runtime_suspend(struct device *dev) +{ + struct phy_mdm6600 *ddata = dev_get_drvdata(dev); + + cancel_delayed_work_sync(&ddata->modem_wake_work); + ddata->awake = false; + + return 0; +} + +static int __maybe_unused phy_mdm6600_runtime_resume(struct device *dev) +{ + struct phy_mdm6600 *ddata = dev_get_drvdata(dev); + + phy_mdm6600_modem_wake(&ddata->modem_wake_work.work); + ddata->awake = true; + + return 0; +} + +static const struct dev_pm_ops phy_mdm6600_pm_ops = { + SET_RUNTIME_PM_OPS(phy_mdm6600_runtime_suspend, + phy_mdm6600_runtime_resume, NULL) +}; + static const struct of_device_id phy_mdm6600_id_table[] = { { .compatible = "motorola,mapphone-mdm6600", }, {}, @@ -464,6 +524,7 @@ static int phy_mdm6600_probe(struct platform_device *pdev) INIT_DELAYED_WORK(&ddata->bootup_work, phy_mdm6600_deferred_power_on); INIT_DELAYED_WORK(&ddata->status_work, phy_mdm6600_status); + INIT_DELAYED_WORK(&ddata->modem_wake_work, phy_mdm6600_modem_wake); init_completion(&ddata->ack); ddata->dev = &pdev->dev; @@ -500,6 +561,24 @@ static int phy_mdm6600_probe(struct platform_device *pdev) */ msleep(PHY_MDM6600_PHY_DELAY_MS + 500); + /* + * Enable PM runtime only after PHY has been powered up properly. + * It is currently only needed after USB suspends mdm6600 and n_gsm + * needs to access the device. We don't want to do this earlier as + * gpio mode0 pin doubles as mdm6600 wake-up gpio. + */ + pm_runtime_use_autosuspend(ddata->dev); + pm_runtime_set_autosuspend_delay(ddata->dev, + MDM6600_MODEM_IDLE_DELAY_MS); + pm_runtime_enable(ddata->dev); + error = pm_runtime_get_sync(ddata->dev); + if (error < 0) { + dev_warn(ddata->dev, "failed to wake modem: %i\n", error); + pm_runtime_put_noidle(ddata->dev); + } + pm_runtime_mark_last_busy(ddata->dev); + pm_runtime_put_autosuspend(ddata->dev); + return 0; cleanup: @@ -512,6 +591,10 @@ static int phy_mdm6600_remove(struct platform_device *pdev) struct phy_mdm6600 *ddata = platform_get_drvdata(pdev); struct gpio_desc *reset_gpio = ddata->ctrl_gpios[PHY_MDM6600_RESET]; + pm_runtime_dont_use_autosuspend(ddata->dev); + pm_runtime_put_sync(ddata->dev); + pm_runtime_disable(ddata->dev); + if (!ddata->running) wait_for_completion_timeout(&ddata->ack, msecs_to_jiffies(PHY_MDM6600_ENABLED_DELAY_MS)); @@ -519,6 +602,7 @@ static int phy_mdm6600_remove(struct platform_device *pdev) gpiod_set_value_cansleep(reset_gpio, 1); phy_mdm6600_device_power_off(ddata); + cancel_delayed_work_sync(&ddata->modem_wake_work); cancel_delayed_work_sync(&ddata->bootup_work); cancel_delayed_work_sync(&ddata->status_work); @@ -530,6 +614,7 @@ static struct platform_driver phy_mdm6600_driver = { .remove = phy_mdm6600_remove, .driver = { .name = "phy-mapphone-mdm6600", + .pm = &phy_mdm6600_pm_ops, .of_match_table = of_match_ptr(phy_mdm6600_id_table), }, }; diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index 09ac8afb97ac..35fd38c5a4a1 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -153,6 +153,9 @@ int phy_pm_runtime_get(struct phy *phy) { int ret; + if (!phy) + return 0; + if (!pm_runtime_enabled(&phy->dev)) return -ENOTSUPP; @@ -168,6 +171,9 @@ int phy_pm_runtime_get_sync(struct phy *phy) { int ret; + if (!phy) + return 0; + if (!pm_runtime_enabled(&phy->dev)) return -ENOTSUPP; @@ -181,6 +187,9 @@ EXPORT_SYMBOL_GPL(phy_pm_runtime_get_sync); int phy_pm_runtime_put(struct phy *phy) { + if (!phy) + return 0; + if (!pm_runtime_enabled(&phy->dev)) return -ENOTSUPP; @@ -190,6 +199,9 @@ EXPORT_SYMBOL_GPL(phy_pm_runtime_put); int phy_pm_runtime_put_sync(struct phy *phy) { + if (!phy) + return 0; + if (!pm_runtime_enabled(&phy->dev)) return -ENOTSUPP; @@ -199,6 +211,9 @@ EXPORT_SYMBOL_GPL(phy_pm_runtime_put_sync); void phy_pm_runtime_allow(struct phy *phy) { + if (!phy) + return; + if (!pm_runtime_enabled(&phy->dev)) return; @@ -208,6 +223,9 @@ EXPORT_SYMBOL_GPL(phy_pm_runtime_allow); void phy_pm_runtime_forbid(struct phy *phy) { + if (!phy) + return; + if (!pm_runtime_enabled(&phy->dev)) return; diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig index 7bfa64baf837..632a0e73ee10 100644 --- a/drivers/phy/qualcomm/Kconfig +++ b/drivers/phy/qualcomm/Kconfig @@ -1,6 +1,15 @@ # -# Phy drivers for Qualcomm platforms +# Phy drivers for Qualcomm and Atheros platforms # +config PHY_ATH79_USB + tristate "Atheros AR71XX/9XXX USB PHY driver" + depends on OF && (ATH79 || COMPILE_TEST) + default y if USB_EHCI_HCD_PLATFORM || USB_OHCI_HCD_PLATFORM + select RESET_CONTROLLER + select GENERIC_PHY + help + Enable this to support the USB PHY on Atheros AR71XX/9XXX SoCs. + config PHY_QCOM_APQ8064_SATA tristate "Qualcomm APQ8064 SATA SerDes/PHY driver" depends on ARCH_QCOM diff --git a/drivers/phy/qualcomm/Makefile b/drivers/phy/qualcomm/Makefile index 9abb7899762a..deb831f453ae 100644 --- a/drivers/phy/qualcomm/Makefile +++ b/drivers/phy/qualcomm/Makefile @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_PHY_ATH79_USB) += phy-ath79-usb.o obj-$(CONFIG_PHY_QCOM_APQ8064_SATA) += phy-qcom-apq8064-sata.o obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o obj-$(CONFIG_PHY_QCOM_QMP) += phy-qcom-qmp.o diff --git a/drivers/phy/qualcomm/phy-ath79-usb.c b/drivers/phy/qualcomm/phy-ath79-usb.c new file mode 100644 index 000000000000..6fd6e07ab345 --- /dev/null +++ b/drivers/phy/qualcomm/phy-ath79-usb.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Atheros AR71XX/9XXX USB PHY driver + * + * Copyright (C) 2015-2018 Alban Bedel <albeu@free.fr> + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/phy/phy.h> +#include <linux/reset.h> + +struct ath79_usb_phy { + struct reset_control *reset; + /* The suspend override logic is inverted, hence the no prefix + * to make the code a bit easier to understand. + */ + struct reset_control *no_suspend_override; +}; + +static int ath79_usb_phy_power_on(struct phy *phy) +{ + struct ath79_usb_phy *priv = phy_get_drvdata(phy); + int err = 0; + + if (priv->no_suspend_override) { + err = reset_control_assert(priv->no_suspend_override); + if (err) + return err; + } + + err = reset_control_deassert(priv->reset); + if (err && priv->no_suspend_override) + reset_control_assert(priv->no_suspend_override); + + return err; +} + +static int ath79_usb_phy_power_off(struct phy *phy) +{ + struct ath79_usb_phy *priv = phy_get_drvdata(phy); + int err = 0; + + err = reset_control_assert(priv->reset); + if (err) + return err; + + if (priv->no_suspend_override) { + err = reset_control_deassert(priv->no_suspend_override); + if (err) + reset_control_deassert(priv->reset); + } + + return err; +} + +static const struct phy_ops ath79_usb_phy_ops = { + .power_on = ath79_usb_phy_power_on, + .power_off = ath79_usb_phy_power_off, + .owner = THIS_MODULE, +}; + +static int ath79_usb_phy_probe(struct platform_device *pdev) +{ + struct ath79_usb_phy *priv; + struct phy *phy; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->reset = devm_reset_control_get(&pdev->dev, "usb-phy"); + if (IS_ERR(priv->reset)) + return PTR_ERR(priv->reset); + + priv->no_suspend_override = devm_reset_control_get_optional( + &pdev->dev, "usb-suspend-override"); + if (IS_ERR(priv->no_suspend_override)) + return PTR_ERR(priv->no_suspend_override); + + phy = devm_phy_create(&pdev->dev, NULL, &ath79_usb_phy_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + phy_set_drvdata(phy, priv); + + return PTR_ERR_OR_ZERO(devm_of_phy_provider_register( + &pdev->dev, of_phy_simple_xlate)); +} + +static const struct of_device_id ath79_usb_phy_of_match[] = { + { .compatible = "qca,ar7100-usb-phy" }, + {} +}; +MODULE_DEVICE_TABLE(of, ath79_usb_phy_of_match); + +static struct platform_driver ath79_usb_phy_driver = { + .probe = ath79_usb_phy_probe, + .driver = { + .of_match_table = ath79_usb_phy_of_match, + .name = "ath79-usb-phy", + } +}; +module_platform_driver(ath79_usb_phy_driver); + +MODULE_DESCRIPTION("ATH79 USB PHY driver"); +MODULE_AUTHOR("Alban Bedel <albeu@free.fr>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.c b/drivers/phy/qualcomm/phy-qcom-qmp.c index 6470c5d61d1c..4c470104a0d6 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp.c @@ -490,6 +490,118 @@ static const struct qmp_phy_init_tbl qmp_v3_usb3_pcs_tbl[] = { QMP_PHY_INIT_CFG(QPHY_V3_PCS_RXEQTRAINING_RUN_TIME, 0x13), }; +static const struct qmp_phy_init_tbl qmp_v3_usb3_uniphy_serdes_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V3_COM_PLL_IVCO, 0x07), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYSCLK_EN_SEL, 0x14), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_BIAS_EN_CLKBUFLR_EN, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_SELECT, 0x30), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYS_CLK_CTRL, 0x02), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_RESETSM_CNTRL2, 0x08), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CMN_CONFIG, 0x06), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SVS_MODE_CLK_SEL, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_HSCLK_SEL, 0x80), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_DEC_START_MODE0, 0x82), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START1_MODE0, 0xab), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START2_MODE0, 0xea), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_DIV_FRAC_START3_MODE0, 0x02), + 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_GAIN1_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_INTEGLOOP_GAIN0_MODE0, 0x3f), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE2_MODE0, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE1_MODE0, 0xc9), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CORECLK_DIV_MODE0, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP3_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP2_MODE0, 0x34), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP1_MODE0, 0x15), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP_EN, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CORE_CLK_EN, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_LOCK_CMP_CFG, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_VCO_TUNE_MAP, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SYSCLK_BUF_ENABLE, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_EN_CENTER, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_PER1, 0x31), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_PER2, 0x01), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_ADJ_PER1, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_ADJ_PER2, 0x00), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_STEP_SIZE1, 0x85), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_SSC_STEP_SIZE2, 0x07), +}; + +static const struct qmp_phy_init_tbl qmp_v3_usb3_uniphy_tx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V3_TX_HIGHZ_DRVR_EN, 0x10), + QMP_PHY_INIT_CFG(QSERDES_V3_TX_RCV_DETECT_LVL_2, 0x12), + QMP_PHY_INIT_CFG(QSERDES_V3_TX_LANE_MODE_1, 0xc6), + QMP_PHY_INIT_CFG(QSERDES_V3_TX_RES_CODE_LANE_OFFSET_RX, 0x06), + QMP_PHY_INIT_CFG(QSERDES_V3_TX_RES_CODE_LANE_OFFSET_TX, 0x06), +}; + +static const struct qmp_phy_init_tbl qmp_v3_usb3_uniphy_rx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V3_RX_VGA_CAL_CNTRL2, 0x0c), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_MODE_00, 0x50), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_FASTLOCK_FO_GAIN, 0x0b), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0e), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL3, 0x4e), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL4, 0x18), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1, 0x77), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_OFFSET_ADAPTOR_CNTRL2, 0x80), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_CNTRL, 0x03), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_DEGLITCH_CNTRL, 0x1c), + QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x75), +}; + +static const struct qmp_phy_init_tbl qmp_v3_usb3_uniphy_pcs_tbl[] = { + /* FLL settings */ + QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNTRL2, 0x83), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNT_VAL_L, 0x09), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNT_VAL_H_TOL, 0xa2), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_MAN_CODE, 0x40), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_FLL_CNTRL1, 0x02), + + /* Lock Det settings */ + QMP_PHY_INIT_CFG(QPHY_V3_PCS_LOCK_DETECT_CONFIG1, 0xd1), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_LOCK_DETECT_CONFIG2, 0x1f), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_LOCK_DETECT_CONFIG3, 0x47), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_POWER_STATE_CONFIG2, 0x1b), + + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RX_SIGDET_LVL, 0xba), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_V0, 0x9f), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_V1, 0x9f), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_V2, 0xb5), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_V3, 0x4c), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_V4, 0x64), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXMGN_LS, 0x6a), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V0, 0x15), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V0, 0x0d), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V1, 0x15), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V1, 0x0d), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V2, 0x15), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V2, 0x0d), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V3, 0x15), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V3, 0x1d), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_V4, 0x15), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V4, 0x0d), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M6DB_LS, 0x15), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_LS, 0x0d), + + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RATE_SLEW_CNTRL, 0x02), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_PWRUP_RESET_DLY_TIME_AUXCLK, 0x04), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_TSYNC_RSYNC_TIME, 0x44), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_PWRUP_RESET_DLY_TIME_AUXCLK, 0x04), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_P1U2_L, 0xe7), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_P1U2_H, 0x03), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_U3_L, 0x40), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RCVR_DTCT_DLY_U3_H, 0x00), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RXEQTRAINING_WAIT_TIME, 0x75), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_LFPS_TX_ECSTART_EQTLOCK, 0x86), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_RXEQTRAINING_RUN_TIME, 0x13), + + QMP_PHY_INIT_CFG(QPHY_V3_PCS_REFGEN_REQ_CONFIG1, 0x21), + QMP_PHY_INIT_CFG(QPHY_V3_PCS_REFGEN_REQ_CONFIG2, 0x60), +}; + + /* struct qmp_phy_cfg - per-PHY initialization config */ struct qmp_phy_cfg { /* phy-type - PCIE/UFS/USB */ @@ -766,6 +878,7 @@ static const struct qmp_phy_cfg qmp_v3_usb3phy_cfg = { .pwrdn_ctrl = SW_PWRDN, .mask_pcs_ready = PHYSTATUS, + .has_pwrdn_delay = true, .pwrdn_delay_min = POWER_DOWN_DELAY_US_MIN, .pwrdn_delay_max = POWER_DOWN_DELAY_US_MAX, @@ -774,6 +887,35 @@ static const struct qmp_phy_cfg qmp_v3_usb3phy_cfg = { .rx_b_lane_offset = 0x400, }; +static const struct qmp_phy_cfg qmp_v3_usb3_uniphy_cfg = { + .type = PHY_TYPE_USB3, + .nlanes = 1, + + .serdes_tbl = qmp_v3_usb3_uniphy_serdes_tbl, + .serdes_tbl_num = ARRAY_SIZE(qmp_v3_usb3_uniphy_serdes_tbl), + .tx_tbl = qmp_v3_usb3_uniphy_tx_tbl, + .tx_tbl_num = ARRAY_SIZE(qmp_v3_usb3_uniphy_tx_tbl), + .rx_tbl = qmp_v3_usb3_uniphy_rx_tbl, + .rx_tbl_num = ARRAY_SIZE(qmp_v3_usb3_uniphy_rx_tbl), + .pcs_tbl = qmp_v3_usb3_uniphy_pcs_tbl, + .pcs_tbl_num = ARRAY_SIZE(qmp_v3_usb3_uniphy_pcs_tbl), + .clk_list = qmp_v3_phy_clk_l, + .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), + .regs = qmp_v3_usb3phy_regs_layout, + + .start_ctrl = SERDES_START | PCS_START, + .pwrdn_ctrl = SW_PWRDN, + .mask_pcs_ready = PHYSTATUS, + + .has_pwrdn_delay = true, + .pwrdn_delay_min = POWER_DOWN_DELAY_US_MIN, + .pwrdn_delay_max = POWER_DOWN_DELAY_US_MAX, +}; + static void qcom_qmp_phy_configure(void __iomem *base, const unsigned int *regs, const struct qmp_phy_init_tbl tbl[], @@ -793,19 +935,6 @@ static void qcom_qmp_phy_configure(void __iomem *base, } } -static int qcom_qmp_phy_poweron(struct phy *phy) -{ - struct qmp_phy *qphy = phy_get_drvdata(phy); - struct qcom_qmp *qmp = qphy->qmp; - int ret; - - ret = clk_prepare_enable(qphy->pipe_clk); - if (ret) - dev_err(qmp->dev, "pipe_clk enable failed, err=%d\n", ret); - - return ret; -} - static int qcom_qmp_phy_com_init(struct qcom_qmp *qmp) { const struct qmp_phy_cfg *cfg = qmp->cfg; @@ -974,6 +1103,12 @@ static int qcom_qmp_phy_init(struct phy *phy) } } + ret = clk_prepare_enable(qphy->pipe_clk); + if (ret) { + dev_err(qmp->dev, "pipe_clk enable failed err=%d\n", ret); + goto err_clk_enable; + } + /* 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 */ @@ -1019,6 +1154,8 @@ static int qcom_qmp_phy_init(struct phy *phy) return ret; err_pcs_ready: + clk_disable_unprepare(qphy->pipe_clk); +err_clk_enable: if (cfg->has_lane_rst) reset_control_assert(qphy->lane_rst); err_lane_rst: @@ -1283,7 +1420,6 @@ 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, }; @@ -1381,8 +1517,11 @@ static const struct of_device_id qcom_qmp_phy_of_match_table[] = { .compatible = "qcom,ipq8074-qmp-pcie-phy", .data = &ipq8074_pciephy_cfg, }, { - .compatible = "qcom,qmp-v3-usb3-phy", + .compatible = "qcom,sdm845-qmp-usb3-phy", .data = &qmp_v3_usb3phy_cfg, + }, { + .compatible = "qcom,sdm845-qmp-usb3-uni-phy", + .data = &qmp_v3_usb3_uniphy_cfg, }, { }, }; diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.h b/drivers/phy/qualcomm/phy-qcom-qmp.h index d1c6905d0439..5d78d43ba9fc 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp.h +++ b/drivers/phy/qualcomm/phy-qcom-qmp.h @@ -214,6 +214,8 @@ #define QSERDES_V3_RX_UCDR_FASTLOCK_FO_GAIN 0x030 #define QSERDES_V3_RX_UCDR_SO_SATURATION_AND_ENABLE 0x034 #define QSERDES_V3_RX_RX_TERM_BW 0x07c +#define QSERDES_V3_RX_VGA_CAL_CNTRL1 0x0bc +#define QSERDES_V3_RX_VGA_CAL_CNTRL2 0x0c0 #define QSERDES_V3_RX_RX_EQ_GAIN2_LSB 0x0c8 #define QSERDES_V3_RX_RX_EQ_GAIN2_MSB 0x0cc #define QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL2 0x0d4 @@ -227,6 +229,7 @@ #define QSERDES_V3_RX_SIGDET_DEGLITCH_CNTRL 0x10c #define QSERDES_V3_RX_RX_BAND 0x110 #define QSERDES_V3_RX_RX_INTERFACE_MODE 0x11c +#define QSERDES_V3_RX_RX_MODE_00 0x164 /* Only for QMP V3 PHY - PCS registers */ #define QPHY_V3_PCS_POWER_DOWN_CONTROL 0x004 @@ -273,6 +276,8 @@ #define QPHY_V3_PCS_FLL_CNT_VAL_H_TOL 0x0d0 #define QPHY_V3_PCS_FLL_MAN_CODE 0x0d4 #define QPHY_V3_PCS_RX_SIGDET_LVL 0x1d8 +#define QPHY_V3_PCS_REFGEN_REQ_CONFIG1 0x20c +#define QPHY_V3_PCS_REFGEN_REQ_CONFIG2 0x210 /* Only for QMP V3 PHY - PCS_MISC registers */ #define QPHY_V3_PCS_MISC_CLAMP_ENABLE 0x0c diff --git a/drivers/phy/qualcomm/phy-qcom-qusb2.c b/drivers/phy/qualcomm/phy-qcom-qusb2.c index 94afeac1a19e..e70e425f26f5 100644 --- a/drivers/phy/qualcomm/phy-qcom-qusb2.c +++ b/drivers/phy/qualcomm/phy-qcom-qusb2.c @@ -20,6 +20,8 @@ #include <linux/reset.h> #include <linux/slab.h> +#include <dt-bindings/phy/phy-qcom-qusb2.h> + #define QUSB2PHY_PLL_TEST 0x04 #define CLK_REF_SEL BIT(7) @@ -60,6 +62,17 @@ #define CORE_RESET BIT(5) #define CORE_RESET_MUX BIT(6) +/* QUSB2PHY_IMP_CTRL1 register bits */ +#define IMP_RES_OFFSET_MASK GENMASK(5, 0) +#define IMP_RES_OFFSET_SHIFT 0x0 + +/* QUSB2PHY_PORT_TUNE1 register bits */ +#define HSTX_TRIM_MASK GENMASK(7, 4) +#define HSTX_TRIM_SHIFT 0x4 +#define PREEMPH_WIDTH_HALF_BIT BIT(2) +#define PREEMPHASIS_EN_MASK GENMASK(1, 0) +#define PREEMPHASIS_EN_SHIFT 0x0 + #define QUSB2PHY_PLL_ANALOG_CONTROLS_TWO 0x04 #define QUSB2PHY_PLL_CLOCK_INVERTERS 0x18c #define QUSB2PHY_PLL_CMODE 0x2c @@ -139,7 +152,7 @@ static const struct qusb2_phy_init_tbl msm8996_init_tbl[] = { QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_PWR_CTRL, 0x00), }; -static const unsigned int qusb2_v2_regs_layout[] = { +static const unsigned int sdm845_regs_layout[] = { [QUSB2PHY_PLL_CORE_INPUT_OVERRIDE] = 0xa8, [QUSB2PHY_PLL_STATUS] = 0x1a0, [QUSB2PHY_PORT_TUNE1] = 0x240, @@ -153,7 +166,7 @@ static const unsigned int qusb2_v2_regs_layout[] = { [QUSB2PHY_INTR_CTRL] = 0x230, }; -static const struct qusb2_phy_init_tbl qusb2_v2_init_tbl[] = { +static const struct qusb2_phy_init_tbl sdm845_init_tbl[] = { QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_ANALOG_CONTROLS_TWO, 0x03), QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_CLOCK_INVERTERS, 0x7c), QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_CMODE, 0x80), @@ -208,10 +221,10 @@ static const struct qusb2_phy_cfg msm8996_phy_cfg = { .autoresume_en = BIT(3), }; -static const struct qusb2_phy_cfg qusb2_v2_phy_cfg = { - .tbl = qusb2_v2_init_tbl, - .tbl_num = ARRAY_SIZE(qusb2_v2_init_tbl), - .regs = qusb2_v2_regs_layout, +static const struct qusb2_phy_cfg sdm845_phy_cfg = { + .tbl = sdm845_init_tbl, + .tbl_num = ARRAY_SIZE(sdm845_init_tbl), + .regs = sdm845_regs_layout, .disable_ctrl = (PWR_CTRL1_VREF_SUPPLY_TRIM | PWR_CTRL1_CLAMP_N_EN | POWER_DOWN), @@ -241,6 +254,15 @@ static const char * const qusb2_phy_vreg_names[] = { * @tcsr: TCSR syscon register map * @cell: nvmem cell containing phy tuning value * + * @override_imp_res_offset: PHY should use different rescode offset + * @imp_res_offset_value: rescode offset to be updated in IMP_CTRL1 register + * @override_hstx_trim: PHY should use different HSTX o/p current value + * @hstx_trim_value: HSTX_TRIM value to be updated in TUNE1 register + * @override_preemphasis: PHY should use different pre-amphasis amplitude + * @preemphasis_level: Amplitude Pre-Emphasis to be updated in TUNE1 register + * @override_preemphasis_width: PHY should use different pre-emphasis duration + * @preemphasis_width: half/full-width Pre-Emphasis updated via TUNE1 + * * @cfg: phy config data * @has_se_clk_scheme: indicate if PHY has single-ended ref clock scheme * @phy_initialized: indicate if PHY has been initialized @@ -259,12 +281,35 @@ struct qusb2_phy { struct regmap *tcsr; struct nvmem_cell *cell; + bool override_imp_res_offset; + u8 imp_res_offset_value; + bool override_hstx_trim; + u8 hstx_trim_value; + bool override_preemphasis; + u8 preemphasis_level; + bool override_preemphasis_width; + u8 preemphasis_width; + const struct qusb2_phy_cfg *cfg; bool has_se_clk_scheme; bool phy_initialized; enum phy_mode mode; }; +static inline void qusb2_write_mask(void __iomem *base, u32 offset, + u32 val, u32 mask) +{ + u32 reg; + + reg = readl(base + offset); + reg &= ~mask; + reg |= val & mask; + writel(reg, base + offset); + + /* Ensure above write is completed */ + readl(base + offset); +} + static inline void qusb2_setbits(void __iomem *base, u32 offset, u32 val) { u32 reg; @@ -305,6 +350,42 @@ void qcom_qusb2_phy_configure(void __iomem *base, } /* + * Update board specific PHY tuning override values if specified from + * device tree. + */ +static void qusb2_phy_override_phy_params(struct qusb2_phy *qphy) +{ + const struct qusb2_phy_cfg *cfg = qphy->cfg; + + if (qphy->override_imp_res_offset) + qusb2_write_mask(qphy->base, QUSB2PHY_IMP_CTRL1, + qphy->imp_res_offset_value << IMP_RES_OFFSET_SHIFT, + IMP_RES_OFFSET_MASK); + + if (qphy->override_hstx_trim) + qusb2_write_mask(qphy->base, cfg->regs[QUSB2PHY_PORT_TUNE1], + qphy->hstx_trim_value << HSTX_TRIM_SHIFT, + HSTX_TRIM_MASK); + + if (qphy->override_preemphasis) + qusb2_write_mask(qphy->base, cfg->regs[QUSB2PHY_PORT_TUNE1], + qphy->preemphasis_level << PREEMPHASIS_EN_SHIFT, + PREEMPHASIS_EN_MASK); + + if (qphy->override_preemphasis_width) { + if (qphy->preemphasis_width == + QUSB2_V2_PREEMPHASIS_WIDTH_HALF_BIT) + qusb2_setbits(qphy->base, + cfg->regs[QUSB2PHY_PORT_TUNE1], + PREEMPH_WIDTH_HALF_BIT); + else + qusb2_clrbits(qphy->base, + cfg->regs[QUSB2PHY_PORT_TUNE1], + PREEMPH_WIDTH_HALF_BIT); + } +} + +/* * Fetches HS Tx tuning value from nvmem and sets the * QUSB2PHY_PORT_TUNE1/2 register. * For error case, skip setting the value and use the default value. @@ -315,6 +396,10 @@ static void qusb2_phy_set_tune2_param(struct qusb2_phy *qphy) const struct qusb2_phy_cfg *cfg = qphy->cfg; u8 *val; + /* efuse register is optional */ + if (!qphy->cell) + return; + /* * Read efuse register having TUNE2/1 parameter's high nibble. * If efuse register shows value as 0x0, or if we fail to find @@ -521,6 +606,9 @@ static int qusb2_phy_init(struct phy *phy) qcom_qusb2_phy_configure(qphy->base, cfg->regs, cfg->tbl, cfg->tbl_num); + /* Override board specific PHY tuning values */ + qusb2_phy_override_phy_params(qphy); + /* Set efuse value for tuning the PHY */ qusb2_phy_set_tune2_param(qphy); @@ -643,8 +731,8 @@ static const struct of_device_id qusb2_phy_of_match_table[] = { .compatible = "qcom,msm8996-qusb2-phy", .data = &msm8996_phy_cfg, }, { - .compatible = "qcom,qusb2-v2-phy", - .data = &qusb2_v2_phy_cfg, + .compatible = "qcom,sdm845-qusb2-phy", + .data = &sdm845_phy_cfg, }, { }, }; @@ -664,6 +752,7 @@ static int qusb2_phy_probe(struct platform_device *pdev) struct resource *res; int ret, i; int num; + u32 value; qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL); if (!qphy) @@ -732,6 +821,31 @@ static int qusb2_phy_probe(struct platform_device *pdev) qphy->cell = NULL; dev_dbg(dev, "failed to lookup tune2 hstx trim value\n"); } + + if (!of_property_read_u32(dev->of_node, "qcom,imp-res-offset-value", + &value)) { + qphy->imp_res_offset_value = (u8)value; + qphy->override_imp_res_offset = true; + } + + if (!of_property_read_u32(dev->of_node, "qcom,hstx-trim-value", + &value)) { + qphy->hstx_trim_value = (u8)value; + qphy->override_hstx_trim = true; + } + + if (!of_property_read_u32(dev->of_node, "qcom,preemphasis-level", + &value)) { + qphy->preemphasis_level = (u8)value; + qphy->override_preemphasis = true; + } + + if (!of_property_read_u32(dev->of_node, "qcom,preemphasis-width", + &value)) { + qphy->preemphasis_width = (u8)value; + qphy->override_preemphasis_width = true; + } + pm_runtime_set_active(dev); pm_runtime_enable(dev); /* diff --git a/drivers/phy/samsung/phy-exynos-mipi-video.c b/drivers/phy/samsung/phy-exynos-mipi-video.c index c198886f80a3..00d89599c67d 100644 --- a/drivers/phy/samsung/phy-exynos-mipi-video.c +++ b/drivers/phy/samsung/phy-exynos-mipi-video.c @@ -231,33 +231,27 @@ struct exynos_mipi_video_phy { static int __set_phy_state(const struct exynos_mipi_phy_desc *data, struct exynos_mipi_video_phy *state, unsigned int on) { - u32 val; + struct regmap *enable_map = state->regmaps[data->enable_map]; + struct regmap *resetn_map = state->regmaps[data->resetn_map]; spin_lock(&state->slock); /* disable in PMU sysreg */ if (!on && data->coupled_phy_id >= 0 && - state->phys[data->coupled_phy_id].phy->power_count == 0) { - regmap_read(state->regmaps[data->enable_map], data->enable_reg, - &val); - val &= ~data->enable_val; - regmap_write(state->regmaps[data->enable_map], data->enable_reg, - val); - } - + state->phys[data->coupled_phy_id].phy->power_count == 0) + regmap_update_bits(enable_map, data->enable_reg, + data->enable_val, 0); /* PHY reset */ - regmap_read(state->regmaps[data->resetn_map], data->resetn_reg, &val); - val = on ? (val | data->resetn_val) : (val & ~data->resetn_val); - regmap_write(state->regmaps[data->resetn_map], data->resetn_reg, val); - + if (on) + regmap_update_bits(resetn_map, data->resetn_reg, + data->resetn_val, data->resetn_val); + else + regmap_update_bits(resetn_map, data->resetn_reg, + data->resetn_val, 0); /* enable in PMU sysreg */ - if (on) { - regmap_read(state->regmaps[data->enable_map], data->enable_reg, - &val); - val |= data->enable_val; - regmap_write(state->regmaps[data->enable_map], data->enable_reg, - val); - } + if (on) + regmap_update_bits(enable_map, data->enable_reg, + data->enable_val, data->enable_val); spin_unlock(&state->slock); diff --git a/drivers/phy/st/phy-stm32-usbphyc.c b/drivers/phy/st/phy-stm32-usbphyc.c index bc4e78a19c91..1255cd1d9a60 100644 --- a/drivers/phy/st/phy-stm32-usbphyc.c +++ b/drivers/phy/st/phy-stm32-usbphyc.c @@ -71,7 +71,6 @@ struct stm32_usbphyc { struct stm32_usbphyc_phy **phys; int nphys; int switch_setup; - bool pll_enabled; }; static inline void stm32_usbphyc_set_bits(void __iomem *reg, u32 bits) @@ -84,7 +83,8 @@ static inline void stm32_usbphyc_clr_bits(void __iomem *reg, u32 bits) writel_relaxed(readl_relaxed(reg) & ~bits, reg); } -static void stm32_usbphyc_get_pll_params(u32 clk_rate, struct pll_params *pll_params) +static void stm32_usbphyc_get_pll_params(u32 clk_rate, + struct pll_params *pll_params) { unsigned long long fvco, ndiv, frac; @@ -271,7 +271,6 @@ static struct phy *stm32_usbphyc_of_xlate(struct device *dev, struct stm32_usbphyc *usbphyc = dev_get_drvdata(dev); struct stm32_usbphyc_phy *usbphyc_phy = NULL; struct device_node *phynode = args->np; - int port = 0; for (port = 0; port < usbphyc->nphys; port++) { @@ -367,8 +366,8 @@ static int stm32_usbphyc_probe(struct platform_device *pdev) if (IS_ERR(phy)) { ret = PTR_ERR(phy); if (ret != -EPROBE_DEFER) - dev_err(dev, - "failed to create phy%d: %d\n", i, ret); + dev_err(dev, "failed to create phy%d: %d\n", + port, ret); goto put_child; } diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index 11aa5902a9ac..de1b4ebe4de2 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -102,19 +102,6 @@ tegra_xusb_pad_find_phy_node(struct tegra_xusb_pad *pad, unsigned int index) return np; } -static int -tegra_xusb_lane_lookup_function(struct tegra_xusb_lane *lane, - const char *function) -{ - unsigned int i; - - for (i = 0; i < lane->soc->num_funcs; i++) - if (strcmp(function, lane->soc->funcs[i]) == 0) - return i; - - return -EINVAL; -} - int tegra_xusb_lane_parse_dt(struct tegra_xusb_lane *lane, struct device_node *np) { @@ -126,7 +113,7 @@ int tegra_xusb_lane_parse_dt(struct tegra_xusb_lane *lane, if (err < 0) return err; - err = tegra_xusb_lane_lookup_function(lane, function); + 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); |