phy: mediatek: add XS-PHY driver

Support XS-PHY for MediaTek SoCs with USB3.1 GEN2 controller

Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com>
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
This commit is contained in:
Chunfeng Yun 2018-05-10 14:10:29 +08:00 committed by Kishon Vijay Abraham I
parent ebd52733e9
commit c1eb8f8384
3 changed files with 610 additions and 0 deletions

View File

@ -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.

View File

@ -3,3 +3,4 @@
#
obj-$(CONFIG_PHY_MTK_TPHY) += phy-mtk-tphy.o
obj-$(CONFIG_PHY_MTK_XSPHY) += phy-mtk-xsphy.o

View File

@ -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");