mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-12 13:34:10 +08:00
6f0577d141
During different reboot cycles, USB PHY PLL may not always lock
during initialization and therefore can cause USB to be not usable.
Hence do not use internal FSM programming sequence for the USB
PHY initialization.
Fixes: 4dcddbb38b
("phy: sr-usb: Add Stingray USB PHY driver")
Signed-off-by: Bharat Gooty <bharat.gooty@broadcom.com>
Signed-off-by: Rayagonda Kokatanur <rayagonda.kokatanur@broadcom.com>
Link: https://lore.kernel.org/r/20200513173947.10919-1-rayagonda.kokatanur@broadcom.com
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
344 lines
7.2 KiB
C
344 lines
7.2 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2016-2018 Broadcom
|
|
*/
|
|
|
|
#include <linux/delay.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/phy/phy.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
enum bcm_usb_phy_version {
|
|
BCM_SR_USB_COMBO_PHY,
|
|
BCM_SR_USB_HS_PHY,
|
|
};
|
|
|
|
enum bcm_usb_phy_reg {
|
|
PLL_CTRL,
|
|
PHY_CTRL,
|
|
PHY_PLL_CTRL,
|
|
};
|
|
|
|
/* USB PHY registers */
|
|
|
|
static const u8 bcm_usb_combo_phy_ss[] = {
|
|
[PLL_CTRL] = 0x18,
|
|
[PHY_CTRL] = 0x14,
|
|
};
|
|
|
|
static const u8 bcm_usb_combo_phy_hs[] = {
|
|
[PLL_CTRL] = 0x0c,
|
|
[PHY_CTRL] = 0x10,
|
|
};
|
|
|
|
static const u8 bcm_usb_hs_phy[] = {
|
|
[PLL_CTRL] = 0x8,
|
|
[PHY_CTRL] = 0xc,
|
|
};
|
|
|
|
enum pll_ctrl_bits {
|
|
PLL_RESETB,
|
|
SSPLL_SUSPEND_EN,
|
|
PLL_SEQ_START,
|
|
PLL_LOCK,
|
|
};
|
|
|
|
static const u8 u3pll_ctrl[] = {
|
|
[PLL_RESETB] = 0,
|
|
[SSPLL_SUSPEND_EN] = 1,
|
|
[PLL_SEQ_START] = 2,
|
|
[PLL_LOCK] = 3,
|
|
};
|
|
|
|
#define HSPLL_PDIV_MASK 0xF
|
|
#define HSPLL_PDIV_VAL 0x1
|
|
|
|
static const u8 u2pll_ctrl[] = {
|
|
[PLL_RESETB] = 5,
|
|
[PLL_LOCK] = 6,
|
|
};
|
|
|
|
enum bcm_usb_phy_ctrl_bits {
|
|
CORERDY,
|
|
PHY_RESETB,
|
|
PHY_PCTL,
|
|
};
|
|
|
|
#define PHY_PCTL_MASK 0xffff
|
|
#define SSPHY_PCTL_VAL 0x0006
|
|
|
|
static const u8 u3phy_ctrl[] = {
|
|
[PHY_RESETB] = 1,
|
|
[PHY_PCTL] = 2,
|
|
};
|
|
|
|
static const u8 u2phy_ctrl[] = {
|
|
[CORERDY] = 0,
|
|
[PHY_RESETB] = 5,
|
|
[PHY_PCTL] = 6,
|
|
};
|
|
|
|
struct bcm_usb_phy_cfg {
|
|
uint32_t type;
|
|
uint32_t version;
|
|
void __iomem *regs;
|
|
struct phy *phy;
|
|
const u8 *offset;
|
|
};
|
|
|
|
#define PLL_LOCK_RETRY_COUNT 1000
|
|
|
|
enum bcm_usb_phy_type {
|
|
USB_HS_PHY,
|
|
USB_SS_PHY,
|
|
};
|
|
|
|
#define NUM_BCM_SR_USB_COMBO_PHYS 2
|
|
|
|
static inline void bcm_usb_reg32_clrbits(void __iomem *addr, uint32_t clear)
|
|
{
|
|
writel(readl(addr) & ~clear, addr);
|
|
}
|
|
|
|
static inline void bcm_usb_reg32_setbits(void __iomem *addr, uint32_t set)
|
|
{
|
|
writel(readl(addr) | set, addr);
|
|
}
|
|
|
|
static int bcm_usb_pll_lock_check(void __iomem *addr, u32 bit)
|
|
{
|
|
int retry;
|
|
u32 rd_data;
|
|
|
|
retry = PLL_LOCK_RETRY_COUNT;
|
|
do {
|
|
rd_data = readl(addr);
|
|
if (rd_data & bit)
|
|
return 0;
|
|
udelay(1);
|
|
} while (--retry > 0);
|
|
|
|
pr_err("%s: FAIL\n", __func__);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static int bcm_usb_ss_phy_init(struct bcm_usb_phy_cfg *phy_cfg)
|
|
{
|
|
int ret = 0;
|
|
void __iomem *regs = phy_cfg->regs;
|
|
const u8 *offset;
|
|
u32 rd_data;
|
|
|
|
offset = phy_cfg->offset;
|
|
|
|
/* Set pctl with mode and soft reset */
|
|
rd_data = readl(regs + offset[PHY_CTRL]);
|
|
rd_data &= ~(PHY_PCTL_MASK << u3phy_ctrl[PHY_PCTL]);
|
|
rd_data |= (SSPHY_PCTL_VAL << u3phy_ctrl[PHY_PCTL]);
|
|
writel(rd_data, regs + offset[PHY_CTRL]);
|
|
|
|
bcm_usb_reg32_clrbits(regs + offset[PLL_CTRL],
|
|
BIT(u3pll_ctrl[SSPLL_SUSPEND_EN]));
|
|
bcm_usb_reg32_setbits(regs + offset[PLL_CTRL],
|
|
BIT(u3pll_ctrl[PLL_SEQ_START]));
|
|
bcm_usb_reg32_setbits(regs + offset[PLL_CTRL],
|
|
BIT(u3pll_ctrl[PLL_RESETB]));
|
|
|
|
/* Maximum timeout for PLL reset done */
|
|
msleep(30);
|
|
|
|
ret = bcm_usb_pll_lock_check(regs + offset[PLL_CTRL],
|
|
BIT(u3pll_ctrl[PLL_LOCK]));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int bcm_usb_hs_phy_init(struct bcm_usb_phy_cfg *phy_cfg)
|
|
{
|
|
int ret = 0;
|
|
void __iomem *regs = phy_cfg->regs;
|
|
const u8 *offset;
|
|
|
|
offset = phy_cfg->offset;
|
|
|
|
bcm_usb_reg32_clrbits(regs + offset[PLL_CTRL],
|
|
BIT(u2pll_ctrl[PLL_RESETB]));
|
|
bcm_usb_reg32_setbits(regs + offset[PLL_CTRL],
|
|
BIT(u2pll_ctrl[PLL_RESETB]));
|
|
|
|
ret = bcm_usb_pll_lock_check(regs + offset[PLL_CTRL],
|
|
BIT(u2pll_ctrl[PLL_LOCK]));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int bcm_usb_phy_reset(struct phy *phy)
|
|
{
|
|
struct bcm_usb_phy_cfg *phy_cfg = phy_get_drvdata(phy);
|
|
void __iomem *regs = phy_cfg->regs;
|
|
const u8 *offset;
|
|
|
|
offset = phy_cfg->offset;
|
|
|
|
if (phy_cfg->type == USB_HS_PHY) {
|
|
bcm_usb_reg32_clrbits(regs + offset[PHY_CTRL],
|
|
BIT(u2phy_ctrl[CORERDY]));
|
|
bcm_usb_reg32_setbits(regs + offset[PHY_CTRL],
|
|
BIT(u2phy_ctrl[CORERDY]));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bcm_usb_phy_init(struct phy *phy)
|
|
{
|
|
struct bcm_usb_phy_cfg *phy_cfg = phy_get_drvdata(phy);
|
|
int ret = -EINVAL;
|
|
|
|
if (phy_cfg->type == USB_SS_PHY)
|
|
ret = bcm_usb_ss_phy_init(phy_cfg);
|
|
else if (phy_cfg->type == USB_HS_PHY)
|
|
ret = bcm_usb_hs_phy_init(phy_cfg);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct phy_ops sr_phy_ops = {
|
|
.init = bcm_usb_phy_init,
|
|
.reset = bcm_usb_phy_reset,
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
static struct phy *bcm_usb_phy_xlate(struct device *dev,
|
|
struct of_phandle_args *args)
|
|
{
|
|
struct bcm_usb_phy_cfg *phy_cfg;
|
|
int phy_idx;
|
|
|
|
phy_cfg = dev_get_drvdata(dev);
|
|
if (!phy_cfg)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
if (phy_cfg->version == BCM_SR_USB_COMBO_PHY) {
|
|
phy_idx = args->args[0];
|
|
|
|
if (WARN_ON(phy_idx > 1))
|
|
return ERR_PTR(-ENODEV);
|
|
|
|
return phy_cfg[phy_idx].phy;
|
|
} else
|
|
return phy_cfg->phy;
|
|
}
|
|
|
|
static int bcm_usb_phy_create(struct device *dev, struct device_node *node,
|
|
void __iomem *regs, uint32_t version)
|
|
{
|
|
struct bcm_usb_phy_cfg *phy_cfg;
|
|
int idx;
|
|
|
|
if (version == BCM_SR_USB_COMBO_PHY) {
|
|
phy_cfg = devm_kzalloc(dev, NUM_BCM_SR_USB_COMBO_PHYS *
|
|
sizeof(struct bcm_usb_phy_cfg),
|
|
GFP_KERNEL);
|
|
if (!phy_cfg)
|
|
return -ENOMEM;
|
|
|
|
for (idx = 0; idx < NUM_BCM_SR_USB_COMBO_PHYS; idx++) {
|
|
phy_cfg[idx].regs = regs;
|
|
phy_cfg[idx].version = version;
|
|
if (idx == 0) {
|
|
phy_cfg[idx].offset = bcm_usb_combo_phy_hs;
|
|
phy_cfg[idx].type = USB_HS_PHY;
|
|
} else {
|
|
phy_cfg[idx].offset = bcm_usb_combo_phy_ss;
|
|
phy_cfg[idx].type = USB_SS_PHY;
|
|
}
|
|
phy_cfg[idx].phy = devm_phy_create(dev, node,
|
|
&sr_phy_ops);
|
|
if (IS_ERR(phy_cfg[idx].phy))
|
|
return PTR_ERR(phy_cfg[idx].phy);
|
|
|
|
phy_set_drvdata(phy_cfg[idx].phy, &phy_cfg[idx]);
|
|
}
|
|
} else if (version == BCM_SR_USB_HS_PHY) {
|
|
phy_cfg = devm_kzalloc(dev, sizeof(struct bcm_usb_phy_cfg),
|
|
GFP_KERNEL);
|
|
if (!phy_cfg)
|
|
return -ENOMEM;
|
|
|
|
phy_cfg->regs = regs;
|
|
phy_cfg->version = version;
|
|
phy_cfg->offset = bcm_usb_hs_phy;
|
|
phy_cfg->type = USB_HS_PHY;
|
|
phy_cfg->phy = devm_phy_create(dev, node, &sr_phy_ops);
|
|
if (IS_ERR(phy_cfg->phy))
|
|
return PTR_ERR(phy_cfg->phy);
|
|
|
|
phy_set_drvdata(phy_cfg->phy, phy_cfg);
|
|
} else
|
|
return -ENODEV;
|
|
|
|
dev_set_drvdata(dev, phy_cfg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id bcm_usb_phy_of_match[] = {
|
|
{
|
|
.compatible = "brcm,sr-usb-combo-phy",
|
|
.data = (void *)BCM_SR_USB_COMBO_PHY,
|
|
},
|
|
{
|
|
.compatible = "brcm,sr-usb-hs-phy",
|
|
.data = (void *)BCM_SR_USB_HS_PHY,
|
|
},
|
|
{ /* sentinel */ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, bcm_usb_phy_of_match);
|
|
|
|
static int bcm_usb_phy_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *dn = dev->of_node;
|
|
const struct of_device_id *of_id;
|
|
struct resource *res;
|
|
void __iomem *regs;
|
|
int ret;
|
|
enum bcm_usb_phy_version version;
|
|
struct phy_provider *phy_provider;
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
regs = devm_ioremap_resource(dev, res);
|
|
if (IS_ERR(regs))
|
|
return PTR_ERR(regs);
|
|
|
|
of_id = of_match_node(bcm_usb_phy_of_match, dn);
|
|
if (of_id)
|
|
version = (enum bcm_usb_phy_version)of_id->data;
|
|
else
|
|
return -ENODEV;
|
|
|
|
ret = bcm_usb_phy_create(dev, dn, regs, version);
|
|
if (ret)
|
|
return ret;
|
|
|
|
phy_provider = devm_of_phy_provider_register(dev, bcm_usb_phy_xlate);
|
|
|
|
return PTR_ERR_OR_ZERO(phy_provider);
|
|
}
|
|
|
|
static struct platform_driver bcm_usb_phy_driver = {
|
|
.driver = {
|
|
.name = "phy-bcm-sr-usb",
|
|
.of_match_table = bcm_usb_phy_of_match,
|
|
},
|
|
.probe = bcm_usb_phy_probe,
|
|
};
|
|
module_platform_driver(bcm_usb_phy_driver);
|
|
|
|
MODULE_AUTHOR("Broadcom");
|
|
MODULE_DESCRIPTION("Broadcom stingray USB Phy driver");
|
|
MODULE_LICENSE("GPL v2");
|