Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+117
View File
@@ -0,0 +1,117 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Phy drivers for Broadcom platforms
#
menu "PHY drivers for Broadcom platforms"
config PHY_BCM63XX_USBH
tristate "BCM63xx USBH PHY driver"
depends on BMIPS_GENERIC || COMPILE_TEST
select GENERIC_PHY
help
Enable this to support the BCM63xx USBH PHY driver.
If unsure, say N.
config PHY_CYGNUS_PCIE
tristate "Broadcom Cygnus PCIe PHY driver"
depends on OF && (ARCH_BCM_CYGNUS || COMPILE_TEST)
select GENERIC_PHY
default ARCH_BCM_CYGNUS
help
Enable this to support the Broadcom Cygnus PCIe PHY.
If unsure, say N.
config PHY_BCM_SR_USB
tristate "Broadcom Stingray USB PHY driver"
depends on OF && (ARCH_BCM_IPROC || COMPILE_TEST)
select GENERIC_PHY
default ARCH_BCM_IPROC
help
Enable this to support the Broadcom Stingray USB PHY
driver. It supports all versions of Superspeed and
Highspeed PHYs.
If unsure, say N.
config BCM_KONA_USB2_PHY
tristate "Broadcom Kona USB2 PHY Driver"
depends on HAS_IOMEM
select GENERIC_PHY
help
Enable this to support the Broadcom Kona USB 2.0 PHY.
config PHY_BCM_NS_USB2
tristate "Broadcom Northstar USB 2.0 PHY Driver"
depends on ARCH_BCM_IPROC || COMPILE_TEST
depends on HAS_IOMEM && OF
select GENERIC_PHY
help
Enable this to support Broadcom USB 2.0 PHY connected to the USB
controller on Northstar family.
config PHY_BCM_NS_USB3
tristate "Broadcom Northstar USB 3.0 PHY Driver"
depends on ARCH_BCM_IPROC || COMPILE_TEST
depends on HAS_IOMEM && OF
depends on PHYLIB
select GENERIC_PHY
help
Enable this to support Broadcom USB 3.0 PHY connected to the USB
controller on Northstar family.
config PHY_NS2_PCIE
tristate "Broadcom Northstar2 PCIe PHY driver"
depends on (OF && MDIO_BUS_MUX_BCM_IPROC) || (COMPILE_TEST && PHYLIB)
select GENERIC_PHY
default ARCH_BCM_IPROC
help
Enable this to support the Broadcom Northstar2 PCIe PHY.
If unsure, say N.
config PHY_NS2_USB_DRD
tristate "Broadcom Northstar2 USB DRD PHY support"
depends on OF && (ARCH_BCM_IPROC || COMPILE_TEST)
select GENERIC_PHY
select EXTCON
default ARCH_BCM_IPROC
help
Enable this to support the Broadcom Northstar2 USB DRD PHY.
This driver initializes the PHY in either HOST or DEVICE mode.
The host or device configuration is read from device tree.
If unsure, say N.
config PHY_BRCM_SATA
tristate "Broadcom SATA PHY driver"
depends on ARCH_BRCMSTB || ARCH_BCM_IPROC || BMIPS_GENERIC || \
ARCH_BCMBCA || COMPILE_TEST
depends on OF
select GENERIC_PHY
default ARCH_BCM_IPROC
help
Enable this to support the Broadcom SATA PHY.
If unsure, say N.
config PHY_BRCM_USB
tristate "Broadcom STB USB PHY driver"
depends on ARCH_BCMBCA || ARCH_BRCMSTB || COMPILE_TEST
depends on OF
select GENERIC_PHY
select SOC_BRCMSTB if ARCH_BRCMSTB
default ARCH_BCMBCA || ARCH_BRCMSTB
help
Enable this to support the Broadcom STB USB PHY.
This driver is required by the USB XHCI, EHCI and OHCI
drivers.
If unsure, say N.
config PHY_BCM_SR_PCIE
tristate "Broadcom Stingray PCIe PHY driver"
depends on OF && (ARCH_BCM_IPROC || COMPILE_TEST)
select GENERIC_PHY
select MFD_SYSCON
default ARCH_BCM_IPROC
help
Enable this to support the Broadcom Stingray PCIe PHY
If unsure, say N.
endmenu
+15
View File
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_PHY_BCM63XX_USBH) += phy-bcm63xx-usbh.o
obj-$(CONFIG_PHY_CYGNUS_PCIE) += phy-bcm-cygnus-pcie.o
obj-$(CONFIG_BCM_KONA_USB2_PHY) += phy-bcm-kona-usb2.o
obj-$(CONFIG_PHY_BCM_NS_USB2) += phy-bcm-ns-usb2.o
obj-$(CONFIG_PHY_BCM_NS_USB3) += phy-bcm-ns-usb3.o
obj-$(CONFIG_PHY_NS2_PCIE) += phy-bcm-ns2-pcie.o
obj-$(CONFIG_PHY_NS2_USB_DRD) += phy-bcm-ns2-usbdrd.o
obj-$(CONFIG_PHY_BRCM_SATA) += phy-brcm-sata.o
obj-$(CONFIG_PHY_BRCM_USB) += phy-brcm-usb-dvr.o
phy-brcm-usb-dvr-objs := phy-brcm-usb.o phy-brcm-usb-init.o phy-brcm-usb-init-synopsys.o
obj-$(CONFIG_PHY_BCM_SR_PCIE) += phy-bcm-sr-pcie.o
obj-$(CONFIG_PHY_BCM_SR_USB) += phy-bcm-sr-usb.o
@@ -0,0 +1,201 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (C) 2015 Broadcom Corporation
#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>
#define PCIE_CFG_OFFSET 0x00
#define PCIE1_PHY_IDDQ_SHIFT 10
#define PCIE0_PHY_IDDQ_SHIFT 2
enum cygnus_pcie_phy_id {
CYGNUS_PHY_PCIE0 = 0,
CYGNUS_PHY_PCIE1,
MAX_NUM_PHYS,
};
struct cygnus_pcie_phy_core;
/**
* struct cygnus_pcie_phy - Cygnus PCIe PHY device
* @core: pointer to the Cygnus PCIe PHY core control
* @id: internal ID to identify the Cygnus PCIe PHY
* @phy: pointer to the kernel PHY device
*/
struct cygnus_pcie_phy {
struct cygnus_pcie_phy_core *core;
enum cygnus_pcie_phy_id id;
struct phy *phy;
};
/**
* struct cygnus_pcie_phy_core - Cygnus PCIe PHY core control
* @dev: pointer to device
* @base: base register
* @lock: mutex to protect access to individual PHYs
* @phys: pointer to Cygnus PHY device
*/
struct cygnus_pcie_phy_core {
struct device *dev;
void __iomem *base;
struct mutex lock;
struct cygnus_pcie_phy phys[MAX_NUM_PHYS];
};
static int cygnus_pcie_power_config(struct cygnus_pcie_phy *phy, bool enable)
{
struct cygnus_pcie_phy_core *core = phy->core;
unsigned shift;
u32 val;
mutex_lock(&core->lock);
switch (phy->id) {
case CYGNUS_PHY_PCIE0:
shift = PCIE0_PHY_IDDQ_SHIFT;
break;
case CYGNUS_PHY_PCIE1:
shift = PCIE1_PHY_IDDQ_SHIFT;
break;
default:
mutex_unlock(&core->lock);
dev_err(core->dev, "PCIe PHY %d invalid\n", phy->id);
return -EINVAL;
}
if (enable) {
val = readl(core->base + PCIE_CFG_OFFSET);
val &= ~BIT(shift);
writel(val, core->base + PCIE_CFG_OFFSET);
/*
* Wait 50 ms for the PCIe Serdes to stabilize after the analog
* front end is brought up
*/
msleep(50);
} else {
val = readl(core->base + PCIE_CFG_OFFSET);
val |= BIT(shift);
writel(val, core->base + PCIE_CFG_OFFSET);
}
mutex_unlock(&core->lock);
dev_dbg(core->dev, "PCIe PHY %d %s\n", phy->id,
enable ? "enabled" : "disabled");
return 0;
}
static int cygnus_pcie_phy_power_on(struct phy *p)
{
struct cygnus_pcie_phy *phy = phy_get_drvdata(p);
return cygnus_pcie_power_config(phy, true);
}
static int cygnus_pcie_phy_power_off(struct phy *p)
{
struct cygnus_pcie_phy *phy = phy_get_drvdata(p);
return cygnus_pcie_power_config(phy, false);
}
static const struct phy_ops cygnus_pcie_phy_ops = {
.power_on = cygnus_pcie_phy_power_on,
.power_off = cygnus_pcie_phy_power_off,
.owner = THIS_MODULE,
};
static int cygnus_pcie_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct cygnus_pcie_phy_core *core;
struct phy_provider *provider;
unsigned cnt = 0;
if (of_get_child_count(node) == 0) {
dev_err(dev, "PHY no child node\n");
return -ENODEV;
}
core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL);
if (!core)
return -ENOMEM;
core->dev = dev;
core->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(core->base))
return PTR_ERR(core->base);
mutex_init(&core->lock);
for_each_available_child_of_node_scoped(node, child) {
unsigned int id;
struct cygnus_pcie_phy *p;
if (of_property_read_u32(child, "reg", &id)) {
dev_err(dev, "missing reg property for %pOFn\n",
child);
return -EINVAL;
}
if (id >= MAX_NUM_PHYS) {
dev_err(dev, "invalid PHY id: %u\n", id);
return -EINVAL;
}
if (core->phys[id].phy) {
dev_err(dev, "duplicated PHY id: %u\n", id);
return -EINVAL;
}
p = &core->phys[id];
p->phy = devm_phy_create(dev, child, &cygnus_pcie_phy_ops);
if (IS_ERR(p->phy)) {
dev_err(dev, "failed to create PHY\n");
return PTR_ERR(p->phy);
}
p->core = core;
p->id = id;
phy_set_drvdata(p->phy, p);
cnt++;
}
dev_set_drvdata(dev, core);
provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
if (IS_ERR(provider)) {
dev_err(dev, "failed to register PHY provider\n");
return PTR_ERR(provider);
}
dev_dbg(dev, "registered %u PCIe PHY(s)\n", cnt);
return 0;
}
static const struct of_device_id cygnus_pcie_phy_match_table[] = {
{ .compatible = "brcm,cygnus-pcie-phy" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, cygnus_pcie_phy_match_table);
static struct platform_driver cygnus_pcie_phy_driver = {
.driver = {
.name = "cygnus-pcie-phy",
.of_match_table = cygnus_pcie_phy_match_table,
},
.probe = cygnus_pcie_phy_probe,
};
module_platform_driver(cygnus_pcie_phy_driver);
MODULE_AUTHOR("Ray Jui <rjui@broadcom.com>");
MODULE_DESCRIPTION("Broadcom Cygnus PCIe PHY driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,145 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* phy-bcm-kona-usb2.c - Broadcom Kona USB2 Phy Driver
*
* Copyright (C) 2013 Linaro Limited
* Matt Porter <mporter@linaro.org>
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#define OTGCTL (0)
#define OTGCTL_OTGSTAT2 BIT(31)
#define OTGCTL_OTGSTAT1 BIT(30)
#define OTGCTL_PRST_N_SW BIT(11)
#define OTGCTL_HRESET_N BIT(10)
#define OTGCTL_UTMI_LINE_STATE1 BIT(9)
#define OTGCTL_UTMI_LINE_STATE0 BIT(8)
#define P1CTL (8)
#define P1CTL_SOFT_RESET BIT(1)
#define P1CTL_NON_DRIVING BIT(0)
struct bcm_kona_usb {
void __iomem *regs;
};
static void bcm_kona_usb_phy_power(struct bcm_kona_usb *phy, int on)
{
u32 val;
val = readl(phy->regs + OTGCTL);
if (on) {
/* Configure and power PHY */
val &= ~(OTGCTL_OTGSTAT2 | OTGCTL_OTGSTAT1 |
OTGCTL_UTMI_LINE_STATE1 | OTGCTL_UTMI_LINE_STATE0);
val |= OTGCTL_PRST_N_SW | OTGCTL_HRESET_N;
} else {
val &= ~(OTGCTL_PRST_N_SW | OTGCTL_HRESET_N);
}
writel(val, phy->regs + OTGCTL);
}
static int bcm_kona_usb_phy_init(struct phy *gphy)
{
struct bcm_kona_usb *phy = phy_get_drvdata(gphy);
u32 val;
/* Soft reset PHY */
val = readl(phy->regs + P1CTL);
val &= ~P1CTL_NON_DRIVING;
val |= P1CTL_SOFT_RESET;
writel(val, phy->regs + P1CTL);
writel(val & ~P1CTL_SOFT_RESET, phy->regs + P1CTL);
/* Reset needs to be asserted for 2ms */
mdelay(2);
writel(val | P1CTL_SOFT_RESET, phy->regs + P1CTL);
return 0;
}
static int bcm_kona_usb_phy_power_on(struct phy *gphy)
{
struct bcm_kona_usb *phy = phy_get_drvdata(gphy);
bcm_kona_usb_phy_power(phy, 1);
return 0;
}
static int bcm_kona_usb_phy_power_off(struct phy *gphy)
{
struct bcm_kona_usb *phy = phy_get_drvdata(gphy);
bcm_kona_usb_phy_power(phy, 0);
return 0;
}
static const struct phy_ops ops = {
.init = bcm_kona_usb_phy_init,
.power_on = bcm_kona_usb_phy_power_on,
.power_off = bcm_kona_usb_phy_power_off,
.owner = THIS_MODULE,
};
static int bcm_kona_usb2_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct bcm_kona_usb *phy;
struct phy *gphy;
struct phy_provider *phy_provider;
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy)
return -ENOMEM;
phy->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(phy->regs))
return PTR_ERR(phy->regs);
platform_set_drvdata(pdev, phy);
gphy = devm_phy_create(dev, NULL, &ops);
if (IS_ERR(gphy))
return PTR_ERR(gphy);
/* The Kona PHY supports an 8-bit wide UTMI interface */
phy_set_bus_width(gphy, 8);
phy_set_drvdata(gphy, phy);
phy_provider = devm_of_phy_provider_register(dev,
of_phy_simple_xlate);
return PTR_ERR_OR_ZERO(phy_provider);
}
static const struct of_device_id bcm_kona_usb2_dt_ids[] = {
{ .compatible = "brcm,kona-usb2-phy" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, bcm_kona_usb2_dt_ids);
static struct platform_driver bcm_kona_usb2_driver = {
.probe = bcm_kona_usb2_probe,
.driver = {
.name = "bcm-kona-usb2",
.of_match_table = bcm_kona_usb2_dt_ids,
},
};
module_platform_driver(bcm_kona_usb2_driver);
MODULE_ALIAS("platform:bcm-kona-usb2");
MODULE_AUTHOR("Matt Porter <mporter@linaro.org>");
MODULE_DESCRIPTION("BCM Kona USB 2.0 PHY driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,142 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Broadcom Northstar USB 2.0 PHY Driver
*
* Copyright (C) 2016 Rafał Miłecki <zajec5@gmail.com>
*/
#include <linux/bcma/bcma.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
struct bcm_ns_usb2 {
struct device *dev;
struct clk *ref_clk;
struct phy *phy;
struct regmap *clkset;
void __iomem *base;
};
static int bcm_ns_usb2_phy_init(struct phy *phy)
{
struct bcm_ns_usb2 *usb2 = phy_get_drvdata(phy);
struct device *dev = usb2->dev;
u32 ref_clk_rate, usb2ctl, usb_pll_ndiv, usb_pll_pdiv;
int err = 0;
err = clk_prepare_enable(usb2->ref_clk);
if (err < 0) {
dev_err(dev, "Failed to prepare ref clock: %d\n", err);
goto err_out;
}
ref_clk_rate = clk_get_rate(usb2->ref_clk);
if (!ref_clk_rate) {
dev_err(dev, "Failed to get ref clock rate\n");
err = -EINVAL;
goto err_clk_off;
}
usb2ctl = readl(usb2->base);
if (usb2ctl & BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_MASK) {
usb_pll_pdiv = usb2ctl;
usb_pll_pdiv &= BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_MASK;
usb_pll_pdiv >>= BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_SHIFT;
} else {
usb_pll_pdiv = 1 << 3;
}
/* Calculate ndiv based on a solid 1920 MHz that is for USB2 PHY */
usb_pll_ndiv = (1920000000 * usb_pll_pdiv) / ref_clk_rate;
/* Unlock DMU PLL settings with some magic value */
regmap_write(usb2->clkset, 0, 0x0000ea68);
/* Write USB 2.0 PLL control setting */
usb2ctl &= ~BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_NDIV_MASK;
usb2ctl |= usb_pll_ndiv << BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_NDIV_SHIFT;
writel(usb2ctl, usb2->base);
/* Lock DMU PLL settings */
regmap_write(usb2->clkset, 0, 0x00000000);
err_clk_off:
clk_disable_unprepare(usb2->ref_clk);
err_out:
return err;
}
static const struct phy_ops ops = {
.init = bcm_ns_usb2_phy_init,
.owner = THIS_MODULE,
};
static int bcm_ns_usb2_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct bcm_ns_usb2 *usb2;
struct phy_provider *phy_provider;
usb2 = devm_kzalloc(&pdev->dev, sizeof(*usb2), GFP_KERNEL);
if (!usb2)
return -ENOMEM;
usb2->dev = dev;
usb2->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(usb2->base)) {
dev_err(dev, "Failed to map control reg\n");
return PTR_ERR(usb2->base);
}
usb2->clkset = syscon_regmap_lookup_by_phandle(dev->of_node,
"brcm,syscon-clkset");
if (IS_ERR(usb2->clkset)) {
dev_err(dev, "Failed to lookup clkset regmap\n");
return PTR_ERR(usb2->clkset);
}
usb2->ref_clk = devm_clk_get(dev, "phy-ref-clk");
if (IS_ERR(usb2->ref_clk)) {
dev_err_probe(dev, PTR_ERR(usb2->ref_clk), "failed to get ref clk\n");
return PTR_ERR(usb2->ref_clk);
}
usb2->phy = devm_phy_create(dev, NULL, &ops);
if (IS_ERR(usb2->phy))
return PTR_ERR(usb2->phy);
phy_set_drvdata(usb2->phy, usb2);
platform_set_drvdata(pdev, usb2);
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
return PTR_ERR_OR_ZERO(phy_provider);
}
static const struct of_device_id bcm_ns_usb2_id_table[] = {
{ .compatible = "brcm,ns-usb2-phy", },
{},
};
MODULE_DEVICE_TABLE(of, bcm_ns_usb2_id_table);
static struct platform_driver bcm_ns_usb2_driver = {
.probe = bcm_ns_usb2_probe,
.driver = {
.name = "bcm_ns_usb2",
.of_match_table = bcm_ns_usb2_id_table,
},
};
module_platform_driver(bcm_ns_usb2_driver);
MODULE_DESCRIPTION("Broadcom Northstar USB 2.0 PHY Driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,245 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Broadcom Northstar USB 3.0 PHY Driver
*
* Copyright (C) 2016 Rafał Miłecki <rafal@milecki.pl>
* Copyright (C) 2016 Broadcom
*
* All magic values used for initialization (and related comments) were obtained
* from Broadcom's SDK:
* Copyright (c) Broadcom Corp, 2012
*/
#include <linux/bcma/bcma.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/iopoll.h>
#include <linux/mdio.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
#include <linux/property.h>
#include <linux/slab.h>
#define BCM_NS_USB3_PHY_BASE_ADDR_REG 0x1f
#define BCM_NS_USB3_PHY_PLL30_BLOCK 0x8000
#define BCM_NS_USB3_PHY_TX_PMD_BLOCK 0x8040
#define BCM_NS_USB3_PHY_PIPE_BLOCK 0x8060
/* Registers of PLL30 block */
#define BCM_NS_USB3_PLL_CONTROL 0x01
#define BCM_NS_USB3_PLLA_CONTROL0 0x0a
#define BCM_NS_USB3_PLLA_CONTROL1 0x0b
/* Registers of TX PMD block */
#define BCM_NS_USB3_TX_PMD_CONTROL1 0x01
/* Registers of PIPE block */
#define BCM_NS_USB3_LFPS_CMP 0x02
#define BCM_NS_USB3_LFPS_DEGLITCH 0x03
enum bcm_ns_family {
BCM_NS_UNKNOWN,
BCM_NS_AX,
BCM_NS_BX,
};
struct bcm_ns_usb3 {
struct device *dev;
enum bcm_ns_family family;
void __iomem *dmp;
struct mdio_device *mdiodev;
struct phy *phy;
};
static const struct of_device_id bcm_ns_usb3_id_table[] = {
{
.compatible = "brcm,ns-ax-usb3-phy",
.data = (int *)BCM_NS_AX,
},
{
.compatible = "brcm,ns-bx-usb3-phy",
.data = (int *)BCM_NS_BX,
},
{},
};
MODULE_DEVICE_TABLE(of, bcm_ns_usb3_id_table);
static int bcm_ns_usb3_mdio_phy_write(struct bcm_ns_usb3 *usb3, u16 reg,
u16 value);
static int bcm_ns_usb3_phy_init_ns_bx(struct bcm_ns_usb3 *usb3)
{
int err;
/* USB3 PLL Block */
err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG,
BCM_NS_USB3_PHY_PLL30_BLOCK);
if (err < 0)
return err;
/* Assert Ana_Pllseq start */
bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLL_CONTROL, 0x1000);
/* Assert CML Divider ratio to 26 */
bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL0, 0x6400);
/* Asserting PLL Reset */
bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL1, 0xc000);
/* Deaaserting PLL Reset */
bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL1, 0x8000);
/* Deasserting USB3 system reset */
writel(0, usb3->dmp + BCMA_RESET_CTL);
/* PLL frequency monitor enable */
bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLL_CONTROL, 0x9000);
/* PIPE Block */
bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG,
BCM_NS_USB3_PHY_PIPE_BLOCK);
/* CMPMAX & CMPMINTH setting */
bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_LFPS_CMP, 0xf30d);
/* DEGLITCH MIN & MAX setting */
bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_LFPS_DEGLITCH, 0x6302);
/* TXPMD block */
bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG,
BCM_NS_USB3_PHY_TX_PMD_BLOCK);
/* Enabling SSC */
bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003);
return 0;
}
static int bcm_ns_usb3_phy_init_ns_ax(struct bcm_ns_usb3 *usb3)
{
int err;
/* PLL30 block */
err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG,
BCM_NS_USB3_PHY_PLL30_BLOCK);
if (err < 0)
return err;
bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL0, 0x6400);
bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, 0x80e0);
bcm_ns_usb3_mdio_phy_write(usb3, 0x02, 0x009c);
/* Enable SSC */
bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG,
BCM_NS_USB3_PHY_TX_PMD_BLOCK);
bcm_ns_usb3_mdio_phy_write(usb3, 0x02, 0x21d3);
bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003);
/* Deasserting USB3 system reset */
writel(0, usb3->dmp + BCMA_RESET_CTL);
return 0;
}
static int bcm_ns_usb3_phy_init(struct phy *phy)
{
struct bcm_ns_usb3 *usb3 = phy_get_drvdata(phy);
int err;
/* Perform USB3 system soft reset */
writel(BCMA_RESET_CTL_RESET, usb3->dmp + BCMA_RESET_CTL);
switch (usb3->family) {
case BCM_NS_AX:
err = bcm_ns_usb3_phy_init_ns_ax(usb3);
break;
case BCM_NS_BX:
err = bcm_ns_usb3_phy_init_ns_bx(usb3);
break;
default:
WARN_ON(1);
err = -ENOTSUPP;
}
return err;
}
static const struct phy_ops ops = {
.init = bcm_ns_usb3_phy_init,
.owner = THIS_MODULE,
};
/**************************************************
* MDIO driver code
**************************************************/
static int bcm_ns_usb3_mdio_phy_write(struct bcm_ns_usb3 *usb3, u16 reg,
u16 value)
{
struct mdio_device *mdiodev = usb3->mdiodev;
return mdiodev_write(mdiodev, reg, value);
}
static int bcm_ns_usb3_mdio_probe(struct mdio_device *mdiodev)
{
struct device *dev = &mdiodev->dev;
struct phy_provider *phy_provider;
struct device_node *syscon_np;
struct bcm_ns_usb3 *usb3;
struct resource res;
int err;
usb3 = devm_kzalloc(dev, sizeof(*usb3), GFP_KERNEL);
if (!usb3)
return -ENOMEM;
usb3->dev = dev;
usb3->mdiodev = mdiodev;
usb3->family = (unsigned long)device_get_match_data(dev);
syscon_np = of_parse_phandle(dev->of_node, "usb3-dmp-syscon", 0);
err = of_address_to_resource(syscon_np, 0, &res);
of_node_put(syscon_np);
if (err)
return err;
usb3->dmp = devm_ioremap_resource(dev, &res);
if (IS_ERR(usb3->dmp))
return PTR_ERR(usb3->dmp);
usb3->phy = devm_phy_create(dev, NULL, &ops);
if (IS_ERR(usb3->phy)) {
dev_err(dev, "Failed to create PHY\n");
return PTR_ERR(usb3->phy);
}
phy_set_drvdata(usb3->phy, usb3);
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
return PTR_ERR_OR_ZERO(phy_provider);
}
static struct mdio_driver bcm_ns_usb3_mdio_driver = {
.mdiodrv = {
.driver = {
.name = "bcm_ns_mdio_usb3",
.of_match_table = bcm_ns_usb3_id_table,
},
},
.probe = bcm_ns_usb3_mdio_probe,
};
mdio_module_driver(bcm_ns_usb3_mdio_driver);
MODULE_DESCRIPTION("Broadcom Northstar USB 3.0 PHY Driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (C) 2016 Broadcom
#include <linux/device.h>
#include <linux/module.h>
#include <linux/of_mdio.h>
#include <linux/mdio.h>
#include <linux/phy.h>
#include <linux/phy/phy.h>
#define BLK_ADDR_REG_OFFSET 0x1f
#define PLL_AFE1_100MHZ_BLK 0x2100
#define PLL_CLK_AMP_OFFSET 0x03
#define PLL_CLK_AMP_2P05V 0x2b18
static int ns2_pci_phy_init(struct phy *p)
{
struct mdio_device *mdiodev = phy_get_drvdata(p);
int rc;
/* select the AFE 100MHz block page */
rc = mdiodev_write(mdiodev, BLK_ADDR_REG_OFFSET, PLL_AFE1_100MHZ_BLK);
if (rc)
goto err;
/* set the 100 MHz reference clock amplitude to 2.05 v */
rc = mdiodev_write(mdiodev, PLL_CLK_AMP_OFFSET, PLL_CLK_AMP_2P05V);
if (rc)
goto err;
return 0;
err:
dev_err(&mdiodev->dev, "Error %d writing to phy\n", rc);
return rc;
}
static const struct phy_ops ns2_pci_phy_ops = {
.init = ns2_pci_phy_init,
.owner = THIS_MODULE,
};
static int ns2_pci_phy_probe(struct mdio_device *mdiodev)
{
struct device *dev = &mdiodev->dev;
struct phy_provider *provider;
struct phy *phy;
phy = devm_phy_create(dev, dev->of_node, &ns2_pci_phy_ops);
if (IS_ERR(phy)) {
dev_err(dev, "failed to create Phy\n");
return PTR_ERR(phy);
}
phy_set_drvdata(phy, mdiodev);
provider = devm_of_phy_provider_register(&phy->dev,
of_phy_simple_xlate);
if (IS_ERR(provider)) {
dev_err(dev, "failed to register Phy provider\n");
return PTR_ERR(provider);
}
return 0;
}
static const struct of_device_id ns2_pci_phy_of_match[] = {
{ .compatible = "brcm,ns2-pcie-phy", },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, ns2_pci_phy_of_match);
static struct mdio_driver ns2_pci_phy_driver = {
.mdiodrv = {
.driver = {
.name = "phy-bcm-ns2-pci",
.of_match_table = ns2_pci_phy_of_match,
},
},
.probe = ns2_pci_phy_probe,
};
mdio_module_driver(ns2_pci_phy_driver);
MODULE_AUTHOR("Broadcom");
MODULE_DESCRIPTION("Broadcom Northstar2 PCI Phy driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:phy-bcm-ns2-pci");
@@ -0,0 +1,416 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (C) 2017 Broadcom
#include <linux/delay.h>
#include <linux/extcon-provider.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/irq.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#define ICFG_DRD_AFE 0x0
#define ICFG_MISC_STAT 0x18
#define ICFG_DRD_P0CTL 0x1C
#define ICFG_STRAP_CTRL 0x20
#define ICFG_FSM_CTRL 0x24
#define ICFG_DEV_BIT BIT(2)
#define IDM_RST_BIT BIT(0)
#define AFE_CORERDY_VDDC BIT(18)
#define PHY_PLL_RESETB BIT(15)
#define PHY_RESETB BIT(14)
#define PHY_PLL_LOCK BIT(0)
#define DRD_DEV_MODE BIT(20)
#define OHCI_OVRCUR_POL BIT(11)
#define ICFG_OFF_MODE BIT(6)
#define PLL_LOCK_RETRY 1000
#define EVT_DEVICE 0
#define EVT_HOST 1
#define DRD_HOST_MODE (BIT(2) | BIT(3))
#define DRD_DEVICE_MODE (BIT(4) | BIT(5))
#define DRD_HOST_VAL 0x803
#define DRD_DEV_VAL 0x807
#define GPIO_DELAY 20
struct ns2_phy_data;
struct ns2_phy_driver {
void __iomem *icfgdrd_regs;
void __iomem *idmdrd_rst_ctrl;
void __iomem *crmu_usb2_ctrl;
void __iomem *usb2h_strap_reg;
struct ns2_phy_data *data;
struct extcon_dev *edev;
struct gpio_desc *vbus_gpiod;
struct gpio_desc *id_gpiod;
int id_irq;
int vbus_irq;
unsigned long debounce_jiffies;
struct delayed_work wq_extcon;
};
struct ns2_phy_data {
struct ns2_phy_driver *driver;
struct phy *phy;
int new_state;
};
static const unsigned int usb_extcon_cable[] = {
EXTCON_USB,
EXTCON_USB_HOST,
EXTCON_NONE,
};
static inline int pll_lock_stat(u32 usb_reg, int reg_mask,
struct ns2_phy_driver *driver)
{
u32 val;
return readl_poll_timeout_atomic(driver->icfgdrd_regs + usb_reg,
val, (val & reg_mask), 1,
PLL_LOCK_RETRY);
}
static int ns2_drd_phy_init(struct phy *phy)
{
struct ns2_phy_data *data = phy_get_drvdata(phy);
struct ns2_phy_driver *driver = data->driver;
u32 val;
val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL);
if (data->new_state == EVT_HOST) {
val &= ~DRD_DEVICE_MODE;
val |= DRD_HOST_MODE;
} else {
val &= ~DRD_HOST_MODE;
val |= DRD_DEVICE_MODE;
}
writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL);
return 0;
}
static int ns2_drd_phy_poweroff(struct phy *phy)
{
struct ns2_phy_data *data = phy_get_drvdata(phy);
struct ns2_phy_driver *driver = data->driver;
u32 val;
val = readl(driver->crmu_usb2_ctrl);
val &= ~AFE_CORERDY_VDDC;
writel(val, driver->crmu_usb2_ctrl);
val = readl(driver->crmu_usb2_ctrl);
val &= ~DRD_DEV_MODE;
writel(val, driver->crmu_usb2_ctrl);
/* Disable Host and Device Mode */
val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL);
val &= ~(DRD_HOST_MODE | DRD_DEVICE_MODE | ICFG_OFF_MODE);
writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL);
return 0;
}
static int ns2_drd_phy_poweron(struct phy *phy)
{
struct ns2_phy_data *data = phy_get_drvdata(phy);
struct ns2_phy_driver *driver = data->driver;
u32 extcon_event = data->new_state;
int ret;
u32 val;
if (extcon_event == EVT_DEVICE) {
writel(DRD_DEV_VAL, driver->icfgdrd_regs + ICFG_DRD_P0CTL);
val = readl(driver->idmdrd_rst_ctrl);
val &= ~IDM_RST_BIT;
writel(val, driver->idmdrd_rst_ctrl);
val = readl(driver->crmu_usb2_ctrl);
val |= (AFE_CORERDY_VDDC | DRD_DEV_MODE);
writel(val, driver->crmu_usb2_ctrl);
/* Bring PHY and PHY_PLL out of Reset */
val = readl(driver->crmu_usb2_ctrl);
val |= (PHY_PLL_RESETB | PHY_RESETB);
writel(val, driver->crmu_usb2_ctrl);
ret = pll_lock_stat(ICFG_MISC_STAT, PHY_PLL_LOCK, driver);
if (ret < 0) {
dev_err(&phy->dev, "Phy PLL lock failed\n");
return ret;
}
} else {
writel(DRD_HOST_VAL, driver->icfgdrd_regs + ICFG_DRD_P0CTL);
val = readl(driver->crmu_usb2_ctrl);
val |= AFE_CORERDY_VDDC;
writel(val, driver->crmu_usb2_ctrl);
ret = pll_lock_stat(ICFG_MISC_STAT, PHY_PLL_LOCK, driver);
if (ret < 0) {
dev_err(&phy->dev, "Phy PLL lock failed\n");
return ret;
}
val = readl(driver->idmdrd_rst_ctrl);
val &= ~IDM_RST_BIT;
writel(val, driver->idmdrd_rst_ctrl);
/* port over current Polarity */
val = readl(driver->usb2h_strap_reg);
val |= OHCI_OVRCUR_POL;
writel(val, driver->usb2h_strap_reg);
}
return 0;
}
static void connect_change(struct ns2_phy_driver *driver)
{
u32 extcon_event;
u32 val;
extcon_event = driver->data->new_state;
val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL);
switch (extcon_event) {
case EVT_DEVICE:
val &= ~(DRD_HOST_MODE | DRD_DEVICE_MODE);
writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL);
val = (val & ~DRD_HOST_MODE) | DRD_DEVICE_MODE;
writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL);
val = readl(driver->icfgdrd_regs + ICFG_DRD_P0CTL);
val |= ICFG_DEV_BIT;
writel(val, driver->icfgdrd_regs + ICFG_DRD_P0CTL);
break;
case EVT_HOST:
val &= ~(DRD_HOST_MODE | DRD_DEVICE_MODE);
writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL);
val = (val & ~DRD_DEVICE_MODE) | DRD_HOST_MODE;
writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL);
val = readl(driver->usb2h_strap_reg);
val |= OHCI_OVRCUR_POL;
writel(val, driver->usb2h_strap_reg);
val = readl(driver->icfgdrd_regs + ICFG_DRD_P0CTL);
val &= ~ICFG_DEV_BIT;
writel(val, driver->icfgdrd_regs + ICFG_DRD_P0CTL);
break;
default:
pr_err("Invalid extcon event\n");
break;
}
}
static void extcon_work(struct work_struct *work)
{
struct ns2_phy_driver *driver;
int vbus;
int id;
driver = container_of(to_delayed_work(work),
struct ns2_phy_driver, wq_extcon);
id = gpiod_get_value_cansleep(driver->id_gpiod);
vbus = gpiod_get_value_cansleep(driver->vbus_gpiod);
if (!id && vbus) { /* Host connected */
extcon_set_state_sync(driver->edev, EXTCON_USB_HOST, true);
pr_debug("Host cable connected\n");
driver->data->new_state = EVT_HOST;
connect_change(driver);
} else if (id && !vbus) { /* Disconnected */
extcon_set_state_sync(driver->edev, EXTCON_USB_HOST, false);
extcon_set_state_sync(driver->edev, EXTCON_USB, false);
pr_debug("Cable disconnected\n");
} else if (id && vbus) { /* Device connected */
extcon_set_state_sync(driver->edev, EXTCON_USB, true);
pr_debug("Device cable connected\n");
driver->data->new_state = EVT_DEVICE;
connect_change(driver);
}
}
static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
struct ns2_phy_driver *driver = dev_id;
queue_delayed_work(system_power_efficient_wq, &driver->wq_extcon,
driver->debounce_jiffies);
return IRQ_HANDLED;
}
static const struct phy_ops ops = {
.init = ns2_drd_phy_init,
.power_on = ns2_drd_phy_poweron,
.power_off = ns2_drd_phy_poweroff,
.owner = THIS_MODULE,
};
static const struct of_device_id ns2_drd_phy_dt_ids[] = {
{ .compatible = "brcm,ns2-drd-phy", },
{ }
};
MODULE_DEVICE_TABLE(of, ns2_drd_phy_dt_ids);
static int ns2_drd_phy_probe(struct platform_device *pdev)
{
struct phy_provider *phy_provider;
struct device *dev = &pdev->dev;
struct ns2_phy_driver *driver;
struct ns2_phy_data *data;
int ret;
u32 val;
driver = devm_kzalloc(dev, sizeof(struct ns2_phy_driver),
GFP_KERNEL);
if (!driver)
return -ENOMEM;
driver->data = devm_kzalloc(dev, sizeof(struct ns2_phy_data),
GFP_KERNEL);
if (!driver->data)
return -ENOMEM;
driver->icfgdrd_regs = devm_platform_ioremap_resource_byname(pdev, "icfg");
if (IS_ERR(driver->icfgdrd_regs))
return PTR_ERR(driver->icfgdrd_regs);
driver->idmdrd_rst_ctrl = devm_platform_ioremap_resource_byname(pdev, "rst-ctrl");
if (IS_ERR(driver->idmdrd_rst_ctrl))
return PTR_ERR(driver->idmdrd_rst_ctrl);
driver->crmu_usb2_ctrl = devm_platform_ioremap_resource_byname(pdev, "crmu-ctrl");
if (IS_ERR(driver->crmu_usb2_ctrl))
return PTR_ERR(driver->crmu_usb2_ctrl);
driver->usb2h_strap_reg = devm_platform_ioremap_resource_byname(pdev, "usb2-strap");
if (IS_ERR(driver->usb2h_strap_reg))
return PTR_ERR(driver->usb2h_strap_reg);
/* create extcon */
driver->id_gpiod = devm_gpiod_get(&pdev->dev, "id", GPIOD_IN);
if (IS_ERR(driver->id_gpiod)) {
dev_err(dev, "failed to get ID GPIO\n");
return PTR_ERR(driver->id_gpiod);
}
driver->vbus_gpiod = devm_gpiod_get(&pdev->dev, "vbus", GPIOD_IN);
if (IS_ERR(driver->vbus_gpiod)) {
dev_err(dev, "failed to get VBUS GPIO\n");
return PTR_ERR(driver->vbus_gpiod);
}
driver->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable);
if (IS_ERR(driver->edev)) {
dev_err(dev, "failed to allocate extcon device\n");
return -ENOMEM;
}
ret = devm_extcon_dev_register(dev, driver->edev);
if (ret < 0) {
dev_err(dev, "failed to register extcon device\n");
return ret;
}
ret = gpiod_set_debounce(driver->id_gpiod, GPIO_DELAY * 1000);
if (ret < 0)
driver->debounce_jiffies = msecs_to_jiffies(GPIO_DELAY);
INIT_DELAYED_WORK(&driver->wq_extcon, extcon_work);
driver->id_irq = gpiod_to_irq(driver->id_gpiod);
if (driver->id_irq < 0) {
dev_err(dev, "failed to get ID IRQ\n");
return driver->id_irq;
}
driver->vbus_irq = gpiod_to_irq(driver->vbus_gpiod);
if (driver->vbus_irq < 0) {
dev_err(dev, "failed to get ID IRQ\n");
return driver->vbus_irq;
}
ret = devm_request_irq(dev, driver->id_irq, gpio_irq_handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"usb_id", driver);
if (ret < 0) {
dev_err(dev, "failed to request handler for ID IRQ\n");
return ret;
}
ret = devm_request_irq(dev, driver->vbus_irq, gpio_irq_handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"usb_vbus", driver);
if (ret < 0) {
dev_err(dev, "failed to request handler for VBUS IRQ\n");
return ret;
}
dev_set_drvdata(dev, driver);
/* Shutdown all ports. They can be powered up as required */
val = readl(driver->crmu_usb2_ctrl);
val &= ~(AFE_CORERDY_VDDC | PHY_RESETB);
writel(val, driver->crmu_usb2_ctrl);
data = driver->data;
data->phy = devm_phy_create(dev, dev->of_node, &ops);
if (IS_ERR(data->phy)) {
dev_err(dev, "Failed to create usb drd phy\n");
return PTR_ERR(data->phy);
}
data->driver = driver;
phy_set_drvdata(data->phy, data);
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
if (IS_ERR(phy_provider)) {
dev_err(dev, "Failed to register as phy provider\n");
return PTR_ERR(phy_provider);
}
platform_set_drvdata(pdev, driver);
queue_delayed_work(system_power_efficient_wq, &driver->wq_extcon,
driver->debounce_jiffies);
return 0;
}
static struct platform_driver ns2_drd_phy_driver = {
.probe = ns2_drd_phy_probe,
.driver = {
.name = "bcm-ns2-usbphy",
.of_match_table = of_match_ptr(ns2_drd_phy_dt_ids),
},
};
module_platform_driver(ns2_drd_phy_driver);
MODULE_ALIAS("platform:bcm-ns2-drd-phy");
MODULE_AUTHOR("Broadcom");
MODULE_DESCRIPTION("Broadcom NS2 USB2 PHY driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,300 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2016-2018 Broadcom
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/mfd/syscon.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
/* we have up to 8 PAXB based RC. The 9th one is always PAXC */
#define SR_NR_PCIE_PHYS 9
#define SR_PAXC_PHY_IDX (SR_NR_PCIE_PHYS - 1)
#define PCIE_PIPEMUX_CFG_OFFSET 0x10c
#define PCIE_PIPEMUX_SELECT_STRAP 0xf
#define CDRU_STRAP_DATA_LSW_OFFSET 0x5c
#define PCIE_PIPEMUX_SHIFT 19
#define PCIE_PIPEMUX_MASK 0xf
#define MHB_MEM_PW_PAXC_OFFSET 0x1c0
#define MHB_PWR_ARR_POWERON 0x8
#define MHB_PWR_ARR_POWEROK 0x4
#define MHB_PWR_POWERON 0x2
#define MHB_PWR_POWEROK 0x1
#define MHB_PWR_STATUS_MASK (MHB_PWR_ARR_POWERON | \
MHB_PWR_ARR_POWEROK | \
MHB_PWR_POWERON | \
MHB_PWR_POWEROK)
struct sr_pcie_phy_core;
/**
* struct sr_pcie_phy - Stingray PCIe PHY
*
* @core: pointer to the Stingray PCIe PHY core control
* @index: PHY index
* @phy: pointer to the kernel PHY device
*/
struct sr_pcie_phy {
struct sr_pcie_phy_core *core;
unsigned int index;
struct phy *phy;
};
/**
* struct sr_pcie_phy_core - Stingray PCIe PHY core control
*
* @dev: pointer to device
* @base: base register of PCIe SS
* @cdru: regmap to the CDRU device
* @mhb: regmap to the MHB device
* @pipemux: pipemuex strap
* @phys: array of PCIe PHYs
*/
struct sr_pcie_phy_core {
struct device *dev;
void __iomem *base;
struct regmap *cdru;
struct regmap *mhb;
u32 pipemux;
struct sr_pcie_phy phys[SR_NR_PCIE_PHYS];
};
/*
* PCIe PIPEMUX lookup table
*
* Each array index represents a PIPEMUX strap setting
* The array element represents a bitmap where a set bit means the PCIe
* core and associated serdes has been enabled as RC and is available for use
*/
static const u8 pipemux_table[] = {
/* PIPEMUX = 0, EP 1x16 */
0x00,
/* PIPEMUX = 1, EP 1x8 + RC 1x8, core 7 */
0x80,
/* PIPEMUX = 2, EP 4x4 */
0x00,
/* PIPEMUX = 3, RC 2x8, cores 0, 7 */
0x81,
/* PIPEMUX = 4, RC 4x4, cores 0, 1, 6, 7 */
0xc3,
/* PIPEMUX = 5, RC 8x2, all 8 cores */
0xff,
/* PIPEMUX = 6, RC 3x4 + 2x2, cores 0, 2, 3, 6, 7 */
0xcd,
/* PIPEMUX = 7, RC 1x4 + 6x2, cores 0, 2, 3, 4, 5, 6, 7 */
0xfd,
/* PIPEMUX = 8, EP 1x8 + RC 4x2, cores 4, 5, 6, 7 */
0xf0,
/* PIPEMUX = 9, EP 1x8 + RC 2x4, cores 6, 7 */
0xc0,
/* PIPEMUX = 10, EP 2x4 + RC 2x4, cores 1, 6 */
0x42,
/* PIPEMUX = 11, EP 2x4 + RC 4x2, cores 2, 3, 4, 5 */
0x3c,
/* PIPEMUX = 12, EP 1x4 + RC 6x2, cores 2, 3, 4, 5, 6, 7 */
0xfc,
/* PIPEMUX = 13, RC 2x4 + RC 1x4 + 2x2, cores 2, 3, 6 */
0x4c,
};
/*
* Return true if the strap setting is valid
*/
static bool pipemux_strap_is_valid(u32 pipemux)
{
return !!(pipemux < ARRAY_SIZE(pipemux_table));
}
/*
* Read the PCIe PIPEMUX from strap
*/
static u32 pipemux_strap_read(struct sr_pcie_phy_core *core)
{
u32 pipemux;
/*
* Read PIPEMUX configuration register to determine the pipemux setting
*
* In the case when the value indicates using HW strap, fall back to
* use HW strap
*/
pipemux = readl(core->base + PCIE_PIPEMUX_CFG_OFFSET);
pipemux &= PCIE_PIPEMUX_MASK;
if (pipemux == PCIE_PIPEMUX_SELECT_STRAP) {
regmap_read(core->cdru, CDRU_STRAP_DATA_LSW_OFFSET, &pipemux);
pipemux >>= PCIE_PIPEMUX_SHIFT;
pipemux &= PCIE_PIPEMUX_MASK;
}
return pipemux;
}
/*
* Given a PIPEMUX strap and PCIe core index, this function returns true if the
* PCIe core needs to be enabled
*/
static bool pcie_core_is_for_rc(struct sr_pcie_phy *phy)
{
struct sr_pcie_phy_core *core = phy->core;
unsigned int core_idx = phy->index;
return !!((pipemux_table[core->pipemux] >> core_idx) & 0x1);
}
static int sr_pcie_phy_init(struct phy *p)
{
struct sr_pcie_phy *phy = phy_get_drvdata(p);
/*
* Check whether this PHY is for root complex or not. If yes, return
* zero so the host driver can proceed to enumeration. If not, return
* an error and that will force the host driver to bail out
*/
if (pcie_core_is_for_rc(phy))
return 0;
return -ENODEV;
}
static int sr_paxc_phy_init(struct phy *p)
{
struct sr_pcie_phy *phy = phy_get_drvdata(p);
struct sr_pcie_phy_core *core = phy->core;
unsigned int core_idx = phy->index;
u32 val;
if (core_idx != SR_PAXC_PHY_IDX)
return -EINVAL;
regmap_read(core->mhb, MHB_MEM_PW_PAXC_OFFSET, &val);
if ((val & MHB_PWR_STATUS_MASK) != MHB_PWR_STATUS_MASK) {
dev_err(core->dev, "PAXC is not powered up\n");
return -ENODEV;
}
return 0;
}
static const struct phy_ops sr_pcie_phy_ops = {
.init = sr_pcie_phy_init,
.owner = THIS_MODULE,
};
static const struct phy_ops sr_paxc_phy_ops = {
.init = sr_paxc_phy_init,
.owner = THIS_MODULE,
};
static struct phy *sr_pcie_phy_xlate(struct device *dev,
const struct of_phandle_args *args)
{
struct sr_pcie_phy_core *core;
int phy_idx;
core = dev_get_drvdata(dev);
if (!core)
return ERR_PTR(-EINVAL);
phy_idx = args->args[0];
if (WARN_ON(phy_idx >= SR_NR_PCIE_PHYS))
return ERR_PTR(-ENODEV);
return core->phys[phy_idx].phy;
}
static int sr_pcie_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct sr_pcie_phy_core *core;
struct phy_provider *provider;
unsigned int phy_idx = 0;
core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL);
if (!core)
return -ENOMEM;
core->dev = dev;
core->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(core->base))
return PTR_ERR(core->base);
core->cdru = syscon_regmap_lookup_by_phandle(node, "brcm,sr-cdru");
if (IS_ERR(core->cdru)) {
dev_err(core->dev, "unable to find CDRU device\n");
return PTR_ERR(core->cdru);
}
core->mhb = syscon_regmap_lookup_by_phandle(node, "brcm,sr-mhb");
if (IS_ERR(core->mhb)) {
dev_err(core->dev, "unable to find MHB device\n");
return PTR_ERR(core->mhb);
}
/* read the PCIe PIPEMUX strap setting */
core->pipemux = pipemux_strap_read(core);
if (!pipemux_strap_is_valid(core->pipemux)) {
dev_err(core->dev, "invalid PCIe PIPEMUX strap %u\n",
core->pipemux);
return -EIO;
}
for (phy_idx = 0; phy_idx < SR_NR_PCIE_PHYS; phy_idx++) {
struct sr_pcie_phy *p = &core->phys[phy_idx];
const struct phy_ops *ops;
if (phy_idx == SR_PAXC_PHY_IDX)
ops = &sr_paxc_phy_ops;
else
ops = &sr_pcie_phy_ops;
p->phy = devm_phy_create(dev, NULL, ops);
if (IS_ERR(p->phy)) {
dev_err(dev, "failed to create PCIe PHY\n");
return PTR_ERR(p->phy);
}
p->core = core;
p->index = phy_idx;
phy_set_drvdata(p->phy, p);
}
dev_set_drvdata(dev, core);
provider = devm_of_phy_provider_register(dev, sr_pcie_phy_xlate);
if (IS_ERR(provider)) {
dev_err(dev, "failed to register PHY provider\n");
return PTR_ERR(provider);
}
return 0;
}
static const struct of_device_id sr_pcie_phy_match_table[] = {
{ .compatible = "brcm,sr-pcie-phy" },
{ }
};
MODULE_DEVICE_TABLE(of, sr_pcie_phy_match_table);
static struct platform_driver sr_pcie_phy_driver = {
.driver = {
.name = "sr-pcie-phy",
.of_match_table = sr_pcie_phy_match_table,
},
.probe = sr_pcie_phy_probe,
};
module_platform_driver(sr_pcie_phy_driver);
MODULE_AUTHOR("Ray Jui <ray.jui@broadcom.com>");
MODULE_DESCRIPTION("Broadcom Stingray PCIe PHY driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,338 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2016-2018 Broadcom
*/
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/iopoll.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)
{
u32 data;
int ret;
ret = readl_poll_timeout_atomic(addr, data, (data & bit), 1,
PLL_LOCK_RETRY_COUNT);
if (ret)
pr_err("%s: FAIL\n", __func__);
return ret;
}
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,
const 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;
void __iomem *regs;
int ret;
enum bcm_usb_phy_version version;
struct phy_provider *phy_provider;
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
of_id = of_match_node(bcm_usb_phy_of_match, dn);
if (of_id)
version = (uintptr_t)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");
@@ -0,0 +1,458 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* BCM6328 USBH PHY Controller Driver
*
* Copyright (C) 2020 Álvaro Fernández Rojas <noltari@gmail.com>
* Copyright (C) 2015 Simon Arlott
*
* Derived from bcm963xx_4.12L.06B_consumer/kernel/linux/arch/mips/bcm963xx/setup.c:
* Copyright (C) 2002 Broadcom Corporation
*
* Derived from OpenWrt patches:
* Copyright (C) 2013 Jonas Gorski <jonas.gorski@gmail.com>
* Copyright (C) 2013 Florian Fainelli <f.fainelli@gmail.com>
* Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr>
*/
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
/* USBH control register offsets */
enum usbh_regs {
USBH_BRT_CONTROL1 = 0,
USBH_BRT_CONTROL2,
USBH_BRT_STATUS1,
USBH_BRT_STATUS2,
USBH_UTMI_CONTROL1,
#define USBH_UC1_DEV_MODE_SEL BIT(0)
USBH_TEST_PORT_CONTROL,
USBH_PLL_CONTROL1,
#define USBH_PLLC_REFCLKSEL_SHIFT 0
#define USBH_PLLC_REFCLKSEL_MASK (0x3 << USBH_PLLC_REFCLKSEL_SHIFT)
#define USBH_PLLC_CLKSEL_SHIFT 2
#define USBH_PLLC_CLKSEL_MASK (0x3 << USBH_PLLC_CLKSEL_MASK)
#define USBH_PLLC_XTAL_PWRDWNB BIT(4)
#define USBH_PLLC_PLL_PWRDWNB BIT(5)
#define USBH_PLLC_PLL_CALEN BIT(6)
#define USBH_PLLC_PHYPLL_BYP BIT(7)
#define USBH_PLLC_PLL_RESET BIT(8)
#define USBH_PLLC_PLL_IDDQ_PWRDN BIT(9)
#define USBH_PLLC_PLL_PWRDN_DELAY BIT(10)
#define USBH_6318_PLLC_PLL_SUSPEND_EN BIT(27)
#define USBH_6318_PLLC_PHYPLL_BYP BIT(29)
#define USBH_6318_PLLC_PLL_RESET BIT(30)
#define USBH_6318_PLLC_PLL_IDDQ_PWRDN BIT(31)
USBH_SWAP_CONTROL,
#define USBH_SC_OHCI_DATA_SWAP BIT(0)
#define USBH_SC_OHCI_ENDIAN_SWAP BIT(1)
#define USBH_SC_OHCI_LOGICAL_ADDR_EN BIT(2)
#define USBH_SC_EHCI_DATA_SWAP BIT(3)
#define USBH_SC_EHCI_ENDIAN_SWAP BIT(4)
#define USBH_SC_EHCI_LOGICAL_ADDR_EN BIT(5)
#define USBH_SC_USB_DEVICE_SEL BIT(6)
USBH_GENERIC_CONTROL,
#define USBH_GC_PLL_SUSPEND_EN BIT(1)
USBH_FRAME_ADJUST_VALUE,
USBH_SETUP,
#define USBH_S_IOC BIT(4)
#define USBH_S_IPP BIT(5)
USBH_MDIO,
USBH_MDIO32,
USBH_USB_SIM_CONTROL,
#define USBH_USC_LADDR_SEL BIT(5)
__USBH_ENUM_SIZE
};
struct bcm63xx_usbh_phy_variant {
/* Registers */
long regs[__USBH_ENUM_SIZE];
/* PLLC bits to set/clear for power on */
u32 power_pllc_clr;
u32 power_pllc_set;
/* Setup bits to set/clear for power on */
u32 setup_clr;
u32 setup_set;
/* Swap Control bits to set */
u32 swapctl_dev_set;
/* Test Port Control value to set if non-zero */
u32 tpc_val;
/* USB Sim Control bits to set */
u32 usc_set;
/* UTMI Control 1 bits to set */
u32 utmictl1_dev_set;
};
struct bcm63xx_usbh_phy {
void __iomem *base;
struct clk *usbh_clk;
struct clk *usb_ref_clk;
struct reset_control *reset;
const struct bcm63xx_usbh_phy_variant *variant;
bool device_mode;
};
static const struct bcm63xx_usbh_phy_variant usbh_bcm6318 = {
.regs = {
[USBH_BRT_CONTROL1] = -1,
[USBH_BRT_CONTROL2] = -1,
[USBH_BRT_STATUS1] = -1,
[USBH_BRT_STATUS2] = -1,
[USBH_UTMI_CONTROL1] = 0x2c,
[USBH_TEST_PORT_CONTROL] = 0x1c,
[USBH_PLL_CONTROL1] = 0x04,
[USBH_SWAP_CONTROL] = 0x0c,
[USBH_GENERIC_CONTROL] = -1,
[USBH_FRAME_ADJUST_VALUE] = 0x08,
[USBH_SETUP] = 0x00,
[USBH_MDIO] = 0x14,
[USBH_MDIO32] = 0x18,
[USBH_USB_SIM_CONTROL] = 0x20,
},
.power_pllc_clr = USBH_6318_PLLC_PLL_IDDQ_PWRDN,
.power_pllc_set = USBH_6318_PLLC_PLL_SUSPEND_EN,
.setup_set = USBH_S_IOC,
.swapctl_dev_set = USBH_SC_USB_DEVICE_SEL,
.usc_set = USBH_USC_LADDR_SEL,
.utmictl1_dev_set = USBH_UC1_DEV_MODE_SEL,
};
static const struct bcm63xx_usbh_phy_variant usbh_bcm6328 = {
.regs = {
[USBH_BRT_CONTROL1] = 0x00,
[USBH_BRT_CONTROL2] = 0x04,
[USBH_BRT_STATUS1] = 0x08,
[USBH_BRT_STATUS2] = 0x0c,
[USBH_UTMI_CONTROL1] = 0x10,
[USBH_TEST_PORT_CONTROL] = 0x14,
[USBH_PLL_CONTROL1] = 0x18,
[USBH_SWAP_CONTROL] = 0x1c,
[USBH_GENERIC_CONTROL] = 0x20,
[USBH_FRAME_ADJUST_VALUE] = 0x24,
[USBH_SETUP] = 0x28,
[USBH_MDIO] = 0x2c,
[USBH_MDIO32] = 0x30,
[USBH_USB_SIM_CONTROL] = 0x34,
},
.setup_set = USBH_S_IOC,
.swapctl_dev_set = USBH_SC_USB_DEVICE_SEL,
.utmictl1_dev_set = USBH_UC1_DEV_MODE_SEL,
};
static const struct bcm63xx_usbh_phy_variant usbh_bcm6358 = {
.regs = {
[USBH_BRT_CONTROL1] = -1,
[USBH_BRT_CONTROL2] = -1,
[USBH_BRT_STATUS1] = -1,
[USBH_BRT_STATUS2] = -1,
[USBH_UTMI_CONTROL1] = -1,
[USBH_TEST_PORT_CONTROL] = 0x24,
[USBH_PLL_CONTROL1] = -1,
[USBH_SWAP_CONTROL] = 0x00,
[USBH_GENERIC_CONTROL] = -1,
[USBH_FRAME_ADJUST_VALUE] = -1,
[USBH_SETUP] = -1,
[USBH_MDIO] = -1,
[USBH_MDIO32] = -1,
[USBH_USB_SIM_CONTROL] = -1,
},
/*
* The magic value comes for the original vendor BSP
* and is needed for USB to work. Datasheet does not
* help, so the magic value is used as-is.
*/
.tpc_val = 0x1c0020,
};
static const struct bcm63xx_usbh_phy_variant usbh_bcm6368 = {
.regs = {
[USBH_BRT_CONTROL1] = 0x00,
[USBH_BRT_CONTROL2] = 0x04,
[USBH_BRT_STATUS1] = 0x08,
[USBH_BRT_STATUS2] = 0x0c,
[USBH_UTMI_CONTROL1] = 0x10,
[USBH_TEST_PORT_CONTROL] = 0x14,
[USBH_PLL_CONTROL1] = 0x18,
[USBH_SWAP_CONTROL] = 0x1c,
[USBH_GENERIC_CONTROL] = -1,
[USBH_FRAME_ADJUST_VALUE] = 0x24,
[USBH_SETUP] = 0x28,
[USBH_MDIO] = 0x2c,
[USBH_MDIO32] = 0x30,
[USBH_USB_SIM_CONTROL] = 0x34,
},
.power_pllc_clr = USBH_PLLC_PLL_IDDQ_PWRDN | USBH_PLLC_PLL_PWRDN_DELAY,
.setup_set = USBH_S_IOC,
.swapctl_dev_set = USBH_SC_USB_DEVICE_SEL,
.utmictl1_dev_set = USBH_UC1_DEV_MODE_SEL,
};
static const struct bcm63xx_usbh_phy_variant usbh_bcm63268 = {
.regs = {
[USBH_BRT_CONTROL1] = 0x00,
[USBH_BRT_CONTROL2] = 0x04,
[USBH_BRT_STATUS1] = 0x08,
[USBH_BRT_STATUS2] = 0x0c,
[USBH_UTMI_CONTROL1] = 0x10,
[USBH_TEST_PORT_CONTROL] = 0x14,
[USBH_PLL_CONTROL1] = 0x18,
[USBH_SWAP_CONTROL] = 0x1c,
[USBH_GENERIC_CONTROL] = 0x20,
[USBH_FRAME_ADJUST_VALUE] = 0x24,
[USBH_SETUP] = 0x28,
[USBH_MDIO] = 0x2c,
[USBH_MDIO32] = 0x30,
[USBH_USB_SIM_CONTROL] = 0x34,
},
.power_pllc_clr = USBH_PLLC_PLL_IDDQ_PWRDN | USBH_PLLC_PLL_PWRDN_DELAY,
.setup_clr = USBH_S_IPP,
.setup_set = USBH_S_IOC,
.swapctl_dev_set = USBH_SC_USB_DEVICE_SEL,
.utmictl1_dev_set = USBH_UC1_DEV_MODE_SEL,
};
static inline bool usbh_has_reg(struct bcm63xx_usbh_phy *usbh, int reg)
{
return (usbh->variant->regs[reg] >= 0);
}
static inline u32 usbh_readl(struct bcm63xx_usbh_phy *usbh, int reg)
{
return __raw_readl(usbh->base + usbh->variant->regs[reg]);
}
static inline void usbh_writel(struct bcm63xx_usbh_phy *usbh, int reg,
u32 value)
{
__raw_writel(value, usbh->base + usbh->variant->regs[reg]);
}
static int bcm63xx_usbh_phy_init(struct phy *phy)
{
struct bcm63xx_usbh_phy *usbh = phy_get_drvdata(phy);
int ret;
ret = clk_prepare_enable(usbh->usbh_clk);
if (ret) {
dev_err(&phy->dev, "unable to enable usbh clock: %d\n", ret);
return ret;
}
ret = clk_prepare_enable(usbh->usb_ref_clk);
if (ret) {
dev_err(&phy->dev, "unable to enable usb_ref clock: %d\n", ret);
clk_disable_unprepare(usbh->usbh_clk);
return ret;
}
ret = reset_control_reset(usbh->reset);
if (ret) {
dev_err(&phy->dev, "unable to reset device: %d\n", ret);
clk_disable_unprepare(usbh->usb_ref_clk);
clk_disable_unprepare(usbh->usbh_clk);
return ret;
}
/* Configure to work in native CPU endian */
if (usbh_has_reg(usbh, USBH_SWAP_CONTROL)) {
u32 val = usbh_readl(usbh, USBH_SWAP_CONTROL);
val |= USBH_SC_EHCI_DATA_SWAP;
val &= ~USBH_SC_EHCI_ENDIAN_SWAP;
val |= USBH_SC_OHCI_DATA_SWAP;
val &= ~USBH_SC_OHCI_ENDIAN_SWAP;
if (usbh->device_mode && usbh->variant->swapctl_dev_set)
val |= usbh->variant->swapctl_dev_set;
usbh_writel(usbh, USBH_SWAP_CONTROL, val);
}
if (usbh_has_reg(usbh, USBH_SETUP)) {
u32 val = usbh_readl(usbh, USBH_SETUP);
val |= usbh->variant->setup_set;
val &= ~usbh->variant->setup_clr;
usbh_writel(usbh, USBH_SETUP, val);
}
if (usbh_has_reg(usbh, USBH_USB_SIM_CONTROL)) {
u32 val = usbh_readl(usbh, USBH_USB_SIM_CONTROL);
val |= usbh->variant->usc_set;
usbh_writel(usbh, USBH_USB_SIM_CONTROL, val);
}
if (usbh->variant->tpc_val &&
usbh_has_reg(usbh, USBH_TEST_PORT_CONTROL))
usbh_writel(usbh, USBH_TEST_PORT_CONTROL,
usbh->variant->tpc_val);
if (usbh->device_mode &&
usbh_has_reg(usbh, USBH_UTMI_CONTROL1) &&
usbh->variant->utmictl1_dev_set) {
u32 val = usbh_readl(usbh, USBH_UTMI_CONTROL1);
val |= usbh->variant->utmictl1_dev_set;
usbh_writel(usbh, USBH_UTMI_CONTROL1, val);
}
return 0;
}
static int bcm63xx_usbh_phy_power_on(struct phy *phy)
{
struct bcm63xx_usbh_phy *usbh = phy_get_drvdata(phy);
if (usbh_has_reg(usbh, USBH_PLL_CONTROL1)) {
u32 val = usbh_readl(usbh, USBH_PLL_CONTROL1);
val |= usbh->variant->power_pllc_set;
val &= ~usbh->variant->power_pllc_clr;
usbh_writel(usbh, USBH_PLL_CONTROL1, val);
}
return 0;
}
static int bcm63xx_usbh_phy_power_off(struct phy *phy)
{
struct bcm63xx_usbh_phy *usbh = phy_get_drvdata(phy);
if (usbh_has_reg(usbh, USBH_PLL_CONTROL1)) {
u32 val = usbh_readl(usbh, USBH_PLL_CONTROL1);
val &= ~usbh->variant->power_pllc_set;
val |= usbh->variant->power_pllc_clr;
usbh_writel(usbh, USBH_PLL_CONTROL1, val);
}
return 0;
}
static int bcm63xx_usbh_phy_exit(struct phy *phy)
{
struct bcm63xx_usbh_phy *usbh = phy_get_drvdata(phy);
clk_disable_unprepare(usbh->usbh_clk);
clk_disable_unprepare(usbh->usb_ref_clk);
return 0;
}
static const struct phy_ops bcm63xx_usbh_phy_ops = {
.exit = bcm63xx_usbh_phy_exit,
.init = bcm63xx_usbh_phy_init,
.power_off = bcm63xx_usbh_phy_power_off,
.power_on = bcm63xx_usbh_phy_power_on,
.owner = THIS_MODULE,
};
static struct phy *bcm63xx_usbh_phy_xlate(struct device *dev,
const struct of_phandle_args *args)
{
struct bcm63xx_usbh_phy *usbh = dev_get_drvdata(dev);
usbh->device_mode = !!args->args[0];
return of_phy_simple_xlate(dev, args);
}
static int bcm63xx_usbh_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct bcm63xx_usbh_phy *usbh;
const struct bcm63xx_usbh_phy_variant *variant;
struct phy *phy;
struct phy_provider *phy_provider;
usbh = devm_kzalloc(dev, sizeof(*usbh), GFP_KERNEL);
if (!usbh)
return -ENOMEM;
variant = device_get_match_data(dev);
if (!variant)
return -EINVAL;
usbh->variant = variant;
usbh->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(usbh->base))
return PTR_ERR(usbh->base);
usbh->reset = devm_reset_control_get_exclusive(dev, NULL);
if (IS_ERR(usbh->reset)) {
if (PTR_ERR(usbh->reset) != -EPROBE_DEFER)
dev_err(dev, "failed to get reset\n");
return PTR_ERR(usbh->reset);
}
usbh->usbh_clk = devm_clk_get_optional(dev, "usbh");
if (IS_ERR(usbh->usbh_clk))
return PTR_ERR(usbh->usbh_clk);
usbh->usb_ref_clk = devm_clk_get_optional(dev, "usb_ref");
if (IS_ERR(usbh->usb_ref_clk))
return PTR_ERR(usbh->usb_ref_clk);
phy = devm_phy_create(dev, NULL, &bcm63xx_usbh_phy_ops);
if (IS_ERR(phy)) {
dev_err(dev, "failed to create PHY\n");
return PTR_ERR(phy);
}
platform_set_drvdata(pdev, usbh);
phy_set_drvdata(phy, usbh);
phy_provider = devm_of_phy_provider_register(dev,
bcm63xx_usbh_phy_xlate);
if (IS_ERR(phy_provider)) {
dev_err(dev, "failed to register PHY provider\n");
return PTR_ERR(phy_provider);
}
dev_dbg(dev, "Registered BCM63xx USB PHY driver\n");
return 0;
}
static const struct of_device_id bcm63xx_usbh_phy_ids[] = {
{ .compatible = "brcm,bcm6318-usbh-phy", .data = &usbh_bcm6318 },
{ .compatible = "brcm,bcm6328-usbh-phy", .data = &usbh_bcm6328 },
{ .compatible = "brcm,bcm6358-usbh-phy", .data = &usbh_bcm6358 },
{ .compatible = "brcm,bcm6362-usbh-phy", .data = &usbh_bcm6368 },
{ .compatible = "brcm,bcm6368-usbh-phy", .data = &usbh_bcm6368 },
{ .compatible = "brcm,bcm63268-usbh-phy", .data = &usbh_bcm63268 },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, bcm63xx_usbh_phy_ids);
static struct platform_driver bcm63xx_usbh_phy_driver = {
.driver = {
.name = "bcm63xx-usbh-phy",
.of_match_table = bcm63xx_usbh_phy_ids,
},
.probe = bcm63xx_usbh_phy_probe,
};
module_platform_driver(bcm63xx_usbh_phy_driver);
MODULE_DESCRIPTION("BCM63xx USBH PHY driver");
MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");
MODULE_AUTHOR("Simon Arlott");
MODULE_LICENSE("GPL");
@@ -0,0 +1,852 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Broadcom SATA3 AHCI Controller PHY Driver
*
* Copyright (C) 2016 Broadcom
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#define SATA_PCB_BANK_OFFSET 0x23c
#define SATA_PCB_REG_OFFSET(ofs) ((ofs) * 4)
#define MAX_PORTS 2
/* Register offset between PHYs in PCB space */
#define SATA_PCB_REG_28NM_SPACE_SIZE 0x1000
/* The older SATA PHY registers duplicated per port registers within the map,
* rather than having a separate map per port.
*/
#define SATA_PCB_REG_40NM_SPACE_SIZE 0x10
/* Register offset between PHYs in PHY control space */
#define SATA_PHY_CTRL_REG_28NM_SPACE_SIZE 0x8
enum brcm_sata_phy_version {
BRCM_SATA_PHY_STB_16NM,
BRCM_SATA_PHY_STB_28NM,
BRCM_SATA_PHY_STB_40NM,
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 {
RXAEQ_MODE_OFF = 0,
RXAEQ_MODE_AUTO,
RXAEQ_MODE_MANUAL,
};
static enum brcm_sata_phy_rxaeq_mode rxaeq_to_val(const char *m)
{
if (!strcmp(m, "auto"))
return RXAEQ_MODE_AUTO;
else if (!strcmp(m, "manual"))
return RXAEQ_MODE_MANUAL;
else
return RXAEQ_MODE_OFF;
}
struct brcm_sata_port {
int portnum;
struct phy *phy;
struct brcm_sata_phy *phy_priv;
bool ssc_en;
enum brcm_sata_phy_rxaeq_mode rxaeq_mode;
u32 rxaeq_val;
u32 tx_amplitude_val;
};
struct brcm_sata_phy {
struct device *dev;
void __iomem *phy_base;
void __iomem *ctrl_base;
enum brcm_sata_phy_version version;
struct brcm_sata_port phys[MAX_PORTS];
};
enum sata_phy_regs {
BLOCK0_REG_BANK = 0x000,
BLOCK0_XGXSSTATUS = 0x81,
BLOCK0_XGXSSTATUS_PLL_LOCK = BIT(12),
BLOCK0_SPARE = 0x8d,
BLOCK0_SPARE_OOB_CLK_SEL_MASK = 0x3,
BLOCK0_SPARE_OOB_CLK_SEL_REFBY2 = 0x1,
BLOCK1_REG_BANK = 0x10,
BLOCK1_TEST_TX = 0x83,
BLOCK1_TEST_TX_AMP_SHIFT = 12,
PLL_REG_BANK_0 = 0x050,
PLL_REG_BANK_0_PLLCONTROL_0 = 0x81,
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,
PLL_ACTRL6 = 0x86,
PLL1_REG_BANK = 0x060,
PLL1_ACTRL2 = 0x82,
PLL1_ACTRL3 = 0x83,
PLL1_ACTRL4 = 0x84,
PLL1_ACTRL5 = 0x85,
PLL1_ACTRL6 = 0x86,
PLL1_ACTRL7 = 0x87,
PLL1_ACTRL8 = 0x88,
TX_REG_BANK = 0x070,
TX_ACTRL0 = 0x80,
TX_ACTRL0_TXPOL_FLIP = BIT(6),
TX_ACTRL5 = 0x85,
TX_ACTRL5_SSC_EN = BIT(11),
AEQRX_REG_BANK_0 = 0xd0,
AEQ_CONTROL1 = 0x81,
AEQ_CONTROL1_ENABLE = BIT(2),
AEQ_CONTROL1_FREEZE = BIT(3),
AEQ_FRC_EQ = 0x83,
AEQ_FRC_EQ_FORCE = BIT(0),
AEQ_FRC_EQ_FORCE_VAL = BIT(1),
AEQ_RFZ_FRC_VAL = BIT(8),
AEQRX_REG_BANK_1 = 0xe0,
AEQRX_SLCAL0_CTRL0 = 0x82,
AEQRX_SLCAL1_CTRL0 = 0x86,
OOB_REG_BANK = 0x150,
OOB1_REG_BANK = 0x160,
OOB_CTRL1 = 0x80,
OOB_CTRL1_BURST_MAX_MASK = 0xf,
OOB_CTRL1_BURST_MAX_SHIFT = 12,
OOB_CTRL1_BURST_MIN_MASK = 0xf,
OOB_CTRL1_BURST_MIN_SHIFT = 8,
OOB_CTRL1_WAKE_IDLE_MAX_MASK = 0xf,
OOB_CTRL1_WAKE_IDLE_MAX_SHIFT = 4,
OOB_CTRL1_WAKE_IDLE_MIN_MASK = 0xf,
OOB_CTRL1_WAKE_IDLE_MIN_SHIFT = 0,
OOB_CTRL2 = 0x81,
OOB_CTRL2_SEL_ENA_SHIFT = 15,
OOB_CTRL2_SEL_ENA_RC_SHIFT = 14,
OOB_CTRL2_RESET_IDLE_MAX_MASK = 0x3f,
OOB_CTRL2_RESET_IDLE_MAX_SHIFT = 8,
OOB_CTRL2_BURST_CNT_MASK = 0x3,
OOB_CTRL2_BURST_CNT_SHIFT = 6,
OOB_CTRL2_RESET_IDLE_MIN_MASK = 0x3f,
OOB_CTRL2_RESET_IDLE_MIN_SHIFT = 0,
TXPMD_REG_BANK = 0x1a0,
TXPMD_CONTROL1 = 0x81,
TXPMD_CONTROL1_TX_SSC_EN_FRC = BIT(0),
TXPMD_CONTROL1_TX_SSC_EN_FRC_VAL = BIT(1),
TXPMD_TX_FREQ_CTRL_CONTROL1 = 0x82,
TXPMD_TX_FREQ_CTRL_CONTROL2 = 0x83,
TXPMD_TX_FREQ_CTRL_CONTROL2_FMIN_MASK = 0x3ff,
TXPMD_TX_FREQ_CTRL_CONTROL3 = 0x84,
TXPMD_TX_FREQ_CTRL_CONTROL3_FMAX_MASK = 0x3ff,
RXPMD_REG_BANK = 0x1c0,
RXPMD_RX_CDR_CONTROL1 = 0x81,
RXPMD_RX_PPM_VAL_MASK = 0x1ff,
RXPMD_RXPMD_EN_FRC = BIT(12),
RXPMD_RXPMD_EN_FRC_VAL = BIT(13),
RXPMD_RX_CDR_CDR_PROP_BW = 0x82,
RXPMD_G_CDR_PROP_BW_MASK = 0x7,
RXPMD_G1_CDR_PROP_BW_SHIFT = 0,
RXPMD_G2_CDR_PROP_BW_SHIFT = 3,
RXPMD_G3_CDR_PROB_BW_SHIFT = 6,
RXPMD_RX_CDR_CDR_ACQ_INTEG_BW = 0x83,
RXPMD_G_CDR_ACQ_INT_BW_MASK = 0x7,
RXPMD_G1_CDR_ACQ_INT_BW_SHIFT = 0,
RXPMD_G2_CDR_ACQ_INT_BW_SHIFT = 3,
RXPMD_G3_CDR_ACQ_INT_BW_SHIFT = 6,
RXPMD_RX_CDR_CDR_LOCK_INTEG_BW = 0x84,
RXPMD_G_CDR_LOCK_INT_BW_MASK = 0x7,
RXPMD_G1_CDR_LOCK_INT_BW_SHIFT = 0,
RXPMD_G2_CDR_LOCK_INT_BW_SHIFT = 3,
RXPMD_G3_CDR_LOCK_INT_BW_SHIFT = 6,
RXPMD_RX_FREQ_MON_CONTROL1 = 0x87,
RXPMD_MON_CORRECT_EN = BIT(8),
RXPMD_MON_MARGIN_VAL_MASK = 0xff,
};
enum sata_phy_ctrl_regs {
PHY_CTRL_1 = 0x0,
PHY_CTRL_1_RESET = BIT(0),
};
static inline void __iomem *brcm_sata_ctrl_base(struct brcm_sata_port *port)
{
struct brcm_sata_phy *priv = port->phy_priv;
u32 size = 0;
switch (priv->version) {
case BRCM_SATA_PHY_IPROC_NS2:
size = SATA_PHY_CTRL_REG_28NM_SPACE_SIZE;
break;
default:
dev_err(priv->dev, "invalid phy version\n");
break;
}
return priv->ctrl_base + (port->portnum * size);
}
static void brcm_sata_phy_wr(struct brcm_sata_port *port, u32 bank,
u32 ofs, u32 msk, u32 value)
{
struct brcm_sata_phy *priv = port->phy_priv;
void __iomem *pcb_base = priv->phy_base;
u32 tmp;
if (priv->version == BRCM_SATA_PHY_STB_40NM)
bank += (port->portnum * SATA_PCB_REG_40NM_SPACE_SIZE);
else
pcb_base += (port->portnum * SATA_PCB_REG_28NM_SPACE_SIZE);
writel(bank, pcb_base + SATA_PCB_BANK_OFFSET);
tmp = readl(pcb_base + SATA_PCB_REG_OFFSET(ofs));
tmp = (tmp & msk) | value;
writel(tmp, pcb_base + SATA_PCB_REG_OFFSET(ofs));
}
static u32 brcm_sata_phy_rd(struct brcm_sata_port *port, u32 bank, u32 ofs)
{
struct brcm_sata_phy *priv = port->phy_priv;
void __iomem *pcb_base = priv->phy_base;
if (priv->version == BRCM_SATA_PHY_STB_40NM)
bank += (port->portnum * SATA_PCB_REG_40NM_SPACE_SIZE);
else
pcb_base += (port->portnum * SATA_PCB_REG_28NM_SPACE_SIZE);
writel(bank, pcb_base + SATA_PCB_BANK_OFFSET);
return readl(pcb_base + SATA_PCB_REG_OFFSET(ofs));
}
/* These defaults were characterized by H/W group */
#define STB_FMIN_VAL_DEFAULT 0x3df
#define STB_FMAX_VAL_DEFAULT 0x3df
#define STB_FMAX_VAL_SSC 0x83
static void brcm_stb_sata_ssc_init(struct brcm_sata_port *port)
{
struct brcm_sata_phy *priv = port->phy_priv;
u32 tmp;
/* override the TX spread spectrum setting */
tmp = TXPMD_CONTROL1_TX_SSC_EN_FRC_VAL | TXPMD_CONTROL1_TX_SSC_EN_FRC;
brcm_sata_phy_wr(port, TXPMD_REG_BANK, TXPMD_CONTROL1, ~tmp, tmp);
/* set fixed min freq */
brcm_sata_phy_wr(port, TXPMD_REG_BANK, TXPMD_TX_FREQ_CTRL_CONTROL2,
~TXPMD_TX_FREQ_CTRL_CONTROL2_FMIN_MASK,
STB_FMIN_VAL_DEFAULT);
/* set fixed max freq depending on SSC config */
if (port->ssc_en) {
dev_info(priv->dev, "enabling SSC on port%d\n", port->portnum);
tmp = STB_FMAX_VAL_SSC;
} else {
tmp = STB_FMAX_VAL_DEFAULT;
}
brcm_sata_phy_wr(port, TXPMD_REG_BANK, TXPMD_TX_FREQ_CTRL_CONTROL3,
~TXPMD_TX_FREQ_CTRL_CONTROL3_FMAX_MASK, tmp);
}
#define AEQ_FRC_EQ_VAL_SHIFT 2
#define AEQ_FRC_EQ_VAL_MASK 0x3f
static int brcm_stb_sata_rxaeq_init(struct brcm_sata_port *port)
{
u32 tmp = 0, reg = 0;
switch (port->rxaeq_mode) {
case RXAEQ_MODE_OFF:
return 0;
case RXAEQ_MODE_AUTO:
reg = AEQ_CONTROL1;
tmp = AEQ_CONTROL1_ENABLE | AEQ_CONTROL1_FREEZE;
break;
case RXAEQ_MODE_MANUAL:
reg = AEQ_FRC_EQ;
tmp = AEQ_FRC_EQ_FORCE | AEQ_FRC_EQ_FORCE_VAL;
if (port->rxaeq_val > AEQ_FRC_EQ_VAL_MASK)
return -EINVAL;
tmp |= port->rxaeq_val << AEQ_FRC_EQ_VAL_SHIFT;
break;
}
brcm_sata_phy_wr(port, AEQRX_REG_BANK_0, reg, ~tmp, tmp);
brcm_sata_phy_wr(port, AEQRX_REG_BANK_1, reg, ~tmp, tmp);
return 0;
}
static int brcm_stb_sata_init(struct brcm_sata_port *port)
{
brcm_stb_sata_ssc_init(port);
return brcm_stb_sata_rxaeq_init(port);
}
static int brcm_stb_sata_16nm_ssc_init(struct brcm_sata_port *port)
{
u32 tmp, value;
/* Reduce CP tail current to 1/16th of its default value */
brcm_sata_phy_wr(port, PLL1_REG_BANK, PLL1_ACTRL6, 0, 0x141);
/* Turn off CP tail current boost */
brcm_sata_phy_wr(port, PLL1_REG_BANK, PLL1_ACTRL8, 0, 0xc006);
/* Set a specific AEQ equalizer value */
tmp = AEQ_FRC_EQ_FORCE_VAL | AEQ_FRC_EQ_FORCE;
brcm_sata_phy_wr(port, AEQRX_REG_BANK_0, AEQ_FRC_EQ,
~(tmp | AEQ_RFZ_FRC_VAL |
AEQ_FRC_EQ_VAL_MASK << AEQ_FRC_EQ_VAL_SHIFT),
tmp | 32 << AEQ_FRC_EQ_VAL_SHIFT);
/* Set RX PPM val center frequency */
if (port->ssc_en)
value = 0x52;
else
value = 0;
brcm_sata_phy_wr(port, RXPMD_REG_BANK, RXPMD_RX_CDR_CONTROL1,
~RXPMD_RX_PPM_VAL_MASK, value);
/* Set proportional loop bandwith Gen1/2/3 */
tmp = RXPMD_G_CDR_PROP_BW_MASK << RXPMD_G1_CDR_PROP_BW_SHIFT |
RXPMD_G_CDR_PROP_BW_MASK << RXPMD_G2_CDR_PROP_BW_SHIFT |
RXPMD_G_CDR_PROP_BW_MASK << RXPMD_G3_CDR_PROB_BW_SHIFT;
if (port->ssc_en)
value = 2 << RXPMD_G1_CDR_PROP_BW_SHIFT |
2 << RXPMD_G2_CDR_PROP_BW_SHIFT |
2 << RXPMD_G3_CDR_PROB_BW_SHIFT;
else
value = 1 << RXPMD_G1_CDR_PROP_BW_SHIFT |
1 << RXPMD_G2_CDR_PROP_BW_SHIFT |
1 << RXPMD_G3_CDR_PROB_BW_SHIFT;
brcm_sata_phy_wr(port, RXPMD_REG_BANK, RXPMD_RX_CDR_CDR_PROP_BW, ~tmp,
value);
/* Set CDR integral loop acquisition bandwidth for Gen1/2/3 */
tmp = RXPMD_G_CDR_ACQ_INT_BW_MASK << RXPMD_G1_CDR_ACQ_INT_BW_SHIFT |
RXPMD_G_CDR_ACQ_INT_BW_MASK << RXPMD_G2_CDR_ACQ_INT_BW_SHIFT |
RXPMD_G_CDR_ACQ_INT_BW_MASK << RXPMD_G3_CDR_ACQ_INT_BW_SHIFT;
if (port->ssc_en)
value = 1 << RXPMD_G1_CDR_ACQ_INT_BW_SHIFT |
1 << RXPMD_G2_CDR_ACQ_INT_BW_SHIFT |
1 << RXPMD_G3_CDR_ACQ_INT_BW_SHIFT;
else
value = 0;
brcm_sata_phy_wr(port, RXPMD_REG_BANK, RXPMD_RX_CDR_CDR_ACQ_INTEG_BW,
~tmp, value);
/* Set CDR integral loop locking bandwidth to 1 for Gen 1/2/3 */
tmp = RXPMD_G_CDR_LOCK_INT_BW_MASK << RXPMD_G1_CDR_LOCK_INT_BW_SHIFT |
RXPMD_G_CDR_LOCK_INT_BW_MASK << RXPMD_G2_CDR_LOCK_INT_BW_SHIFT |
RXPMD_G_CDR_LOCK_INT_BW_MASK << RXPMD_G3_CDR_LOCK_INT_BW_SHIFT;
if (port->ssc_en)
value = 1 << RXPMD_G1_CDR_LOCK_INT_BW_SHIFT |
1 << RXPMD_G2_CDR_LOCK_INT_BW_SHIFT |
1 << RXPMD_G3_CDR_LOCK_INT_BW_SHIFT;
else
value = 0;
brcm_sata_phy_wr(port, RXPMD_REG_BANK, RXPMD_RX_CDR_CDR_LOCK_INTEG_BW,
~tmp, value);
/* Set no guard band and clamp CDR */
tmp = RXPMD_MON_CORRECT_EN | RXPMD_MON_MARGIN_VAL_MASK;
if (port->ssc_en)
value = 0x51;
else
value = 0;
brcm_sata_phy_wr(port, RXPMD_REG_BANK, RXPMD_RX_FREQ_MON_CONTROL1,
~tmp, RXPMD_MON_CORRECT_EN | value);
tmp = GENMASK(15, 12);
switch (port->tx_amplitude_val) {
case 400:
value = BIT(12) | BIT(13);
break;
case 500:
value = BIT(13);
break;
case 600:
value = BIT(12);
break;
case 800:
value = 0;
break;
default:
value = tmp;
break;
}
if (value != tmp)
brcm_sata_phy_wr(port, BLOCK1_REG_BANK, BLOCK1_TEST_TX, ~tmp,
value);
/* Turn on/off SSC */
brcm_sata_phy_wr(port, TX_REG_BANK, TX_ACTRL5, ~TX_ACTRL5_SSC_EN,
port->ssc_en ? TX_ACTRL5_SSC_EN : 0);
return 0;
}
static int brcm_stb_sata_16nm_init(struct brcm_sata_port *port)
{
return brcm_stb_sata_16nm_ssc_init(port);
}
/* NS2 SATA PLL1 defaults were characterized by H/W group */
#define NS2_PLL1_ACTRL2_MAGIC 0x1df8
#define NS2_PLL1_ACTRL3_MAGIC 0x2b00
#define NS2_PLL1_ACTRL4_MAGIC 0x8824
static int brcm_ns2_sata_init(struct brcm_sata_port *port)
{
int try;
unsigned int val;
void __iomem *ctrl_base = brcm_sata_ctrl_base(port);
struct device *dev = port->phy_priv->dev;
/* Configure OOB control */
val = 0x0;
val |= (0xc << OOB_CTRL1_BURST_MAX_SHIFT);
val |= (0x4 << OOB_CTRL1_BURST_MIN_SHIFT);
val |= (0x9 << OOB_CTRL1_WAKE_IDLE_MAX_SHIFT);
val |= (0x3 << OOB_CTRL1_WAKE_IDLE_MIN_SHIFT);
brcm_sata_phy_wr(port, OOB_REG_BANK, OOB_CTRL1, 0x0, val);
val = 0x0;
val |= (0x1b << OOB_CTRL2_RESET_IDLE_MAX_SHIFT);
val |= (0x2 << OOB_CTRL2_BURST_CNT_SHIFT);
val |= (0x9 << OOB_CTRL2_RESET_IDLE_MIN_SHIFT);
brcm_sata_phy_wr(port, OOB_REG_BANK, OOB_CTRL2, 0x0, val);
/* Configure PHY PLL register bank 1 */
val = NS2_PLL1_ACTRL2_MAGIC;
brcm_sata_phy_wr(port, PLL1_REG_BANK, PLL1_ACTRL2, 0x0, val);
val = NS2_PLL1_ACTRL3_MAGIC;
brcm_sata_phy_wr(port, PLL1_REG_BANK, PLL1_ACTRL3, 0x0, val);
val = NS2_PLL1_ACTRL4_MAGIC;
brcm_sata_phy_wr(port, PLL1_REG_BANK, PLL1_ACTRL4, 0x0, val);
/* Configure PHY BLOCK0 register bank */
/* Set oob_clk_sel to refclk/2 */
brcm_sata_phy_wr(port, BLOCK0_REG_BANK, BLOCK0_SPARE,
~BLOCK0_SPARE_OOB_CLK_SEL_MASK,
BLOCK0_SPARE_OOB_CLK_SEL_REFBY2);
/* Strobe PHY reset using PHY control register */
writel(PHY_CTRL_1_RESET, ctrl_base + PHY_CTRL_1);
mdelay(1);
writel(0x0, ctrl_base + PHY_CTRL_1);
mdelay(1);
/* Wait for PHY PLL lock by polling pll_lock bit */
try = 50;
while (try) {
val = brcm_sata_phy_rd(port, BLOCK0_REG_BANK,
BLOCK0_XGXSSTATUS);
if (val & 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_nsp_sata_init(struct brcm_sata_port *port)
{
struct device *dev = port->phy_priv->dev;
unsigned int oob_bank;
unsigned int val, try;
/* Configure OOB control */
if (port->portnum == 0)
oob_bank = OOB_REG_BANK;
else if (port->portnum == 1)
oob_bank = OOB1_REG_BANK;
else
return -EINVAL;
val = 0x0;
val |= (0x0f << OOB_CTRL1_BURST_MAX_SHIFT);
val |= (0x06 << OOB_CTRL1_BURST_MIN_SHIFT);
val |= (0x0f << OOB_CTRL1_WAKE_IDLE_MAX_SHIFT);
val |= (0x06 << OOB_CTRL1_WAKE_IDLE_MIN_SHIFT);
brcm_sata_phy_wr(port, oob_bank, OOB_CTRL1, 0x0, val);
val = 0x0;
val |= (0x2e << OOB_CTRL2_RESET_IDLE_MAX_SHIFT);
val |= (0x02 << OOB_CTRL2_BURST_CNT_SHIFT);
val |= (0x16 << OOB_CTRL2_RESET_IDLE_MIN_SHIFT);
brcm_sata_phy_wr(port, oob_bank, OOB_CTRL2, 0x0, val);
brcm_sata_phy_wr(port, PLL_REG_BANK_0, PLL_ACTRL2,
~(PLL_ACTRL2_SELDIV_MASK << PLL_ACTRL2_SELDIV_SHIFT),
0x0c << PLL_ACTRL2_SELDIV_SHIFT);
brcm_sata_phy_wr(port, PLL_REG_BANK_0, PLL_CAP_CONTROL,
0xff0, 0x4f0);
val = PLLCONTROL_0_FREQ_DET_RESTART | PLLCONTROL_0_FREQ_MONITOR;
brcm_sata_phy_wr(port, PLL_REG_BANK_0, PLL_REG_BANK_0_PLLCONTROL_0,
~val, val);
val = PLLCONTROL_0_SEQ_START;
brcm_sata_phy_wr(port, PLL_REG_BANK_0, PLL_REG_BANK_0_PLLCONTROL_0,
~val, 0);
mdelay(10);
brcm_sata_phy_wr(port, PLL_REG_BANK_0, PLL_REG_BANK_0_PLLCONTROL_0,
~val, val);
/* Wait for pll_seq_done bit */
try = 50;
while (--try) {
val = brcm_sata_phy_rd(port, BLOCK0_REG_BANK,
BLOCK0_XGXSSTATUS);
if (val & BLOCK0_XGXSSTATUS_PLL_LOCK)
break;
msleep(20);
}
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;
}
/* SR PHY PLL0 registers */
#define SR_PLL0_ACTRL6_MAGIC 0xa
/* SR PHY PLL1 registers */
#define SR_PLL1_ACTRL2_MAGIC 0x32
#define SR_PLL1_ACTRL3_MAGIC 0x2
#define SR_PLL1_ACTRL4_MAGIC 0x3e8
static int brcm_sr_sata_init(struct brcm_sata_port *port)
{
struct device *dev = port->phy_priv->dev;
unsigned int val, try;
/* Configure PHY PLL register bank 1 */
val = SR_PLL1_ACTRL2_MAGIC;
brcm_sata_phy_wr(port, PLL1_REG_BANK, PLL1_ACTRL2, 0x0, val);
val = SR_PLL1_ACTRL3_MAGIC;
brcm_sata_phy_wr(port, PLL1_REG_BANK, PLL1_ACTRL3, 0x0, val);
val = SR_PLL1_ACTRL4_MAGIC;
brcm_sata_phy_wr(port, PLL1_REG_BANK, PLL1_ACTRL4, 0x0, val);
/* Configure PHY PLL register bank 0 */
val = SR_PLL0_ACTRL6_MAGIC;
brcm_sata_phy_wr(port, PLL_REG_BANK_0, PLL_ACTRL6, 0x0, val);
/* Wait for PHY PLL lock by polling pll_lock bit */
try = 50;
do {
val = brcm_sata_phy_rd(port, BLOCK0_REG_BANK,
BLOCK0_XGXSSTATUS);
if (val & BLOCK0_XGXSSTATUS_PLL_LOCK)
break;
msleep(20);
try--;
} while (try);
if ((val & BLOCK0_XGXSSTATUS_PLL_LOCK) == 0) {
/* PLL did not lock; give up */
dev_err(dev, "port%d PLL did not lock\n", port->portnum);
return -ETIMEDOUT;
}
/* Invert Tx polarity */
brcm_sata_phy_wr(port, TX_REG_BANK, TX_ACTRL0,
~TX_ACTRL0_TXPOL_FLIP, TX_ACTRL0_TXPOL_FLIP);
/* Configure OOB control to handle 100MHz reference clock */
val = ((0xc << OOB_CTRL1_BURST_MAX_SHIFT) |
(0x4 << OOB_CTRL1_BURST_MIN_SHIFT) |
(0x8 << OOB_CTRL1_WAKE_IDLE_MAX_SHIFT) |
(0x3 << OOB_CTRL1_WAKE_IDLE_MIN_SHIFT));
brcm_sata_phy_wr(port, OOB_REG_BANK, OOB_CTRL1, 0x0, val);
val = ((0x1b << OOB_CTRL2_RESET_IDLE_MAX_SHIFT) |
(0x2 << OOB_CTRL2_BURST_CNT_SHIFT) |
(0x9 << OOB_CTRL2_RESET_IDLE_MIN_SHIFT));
brcm_sata_phy_wr(port, OOB_REG_BANK, OOB_CTRL2, 0x0, val);
return 0;
}
static int brcm_dsl_sata_init(struct brcm_sata_port *port)
{
struct device *dev = port->phy_priv->dev;
unsigned int try;
u32 tmp;
brcm_sata_phy_wr(port, PLL1_REG_BANK, PLL1_ACTRL7, 0, 0x873);
brcm_sata_phy_wr(port, PLL1_REG_BANK, PLL1_ACTRL6, 0, 0xc000);
brcm_sata_phy_wr(port, PLL_REG_BANK_0, PLL_REG_BANK_0_PLLCONTROL_0,
0, 0x3089);
usleep_range(1000, 2000);
brcm_sata_phy_wr(port, PLL_REG_BANK_0, PLL_REG_BANK_0_PLLCONTROL_0,
0, 0x3088);
usleep_range(1000, 2000);
brcm_sata_phy_wr(port, AEQRX_REG_BANK_1, AEQRX_SLCAL0_CTRL0,
0, 0x3000);
brcm_sata_phy_wr(port, AEQRX_REG_BANK_1, AEQRX_SLCAL1_CTRL0,
0, 0x3000);
usleep_range(1000, 2000);
brcm_sata_phy_wr(port, PLL_REG_BANK_0, PLL_CAP_CHARGE_TIME, 0, 0x32);
brcm_sata_phy_wr(port, PLL_REG_BANK_0, PLL_VCO_CAL_THRESH, 0, 0xa);
brcm_sata_phy_wr(port, 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(port, 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;
struct brcm_sata_port *port = phy_get_drvdata(phy);
switch (port->phy_priv->version) {
case BRCM_SATA_PHY_STB_16NM:
rc = brcm_stb_sata_16nm_init(port);
break;
case BRCM_SATA_PHY_STB_28NM:
case BRCM_SATA_PHY_STB_40NM:
rc = brcm_stb_sata_init(port);
break;
case BRCM_SATA_PHY_IPROC_NS2:
rc = brcm_ns2_sata_init(port);
break;
case BRCM_SATA_PHY_IPROC_NSP:
rc = brcm_nsp_sata_init(port);
break;
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;
}
return rc;
}
static void brcm_stb_sata_calibrate(struct brcm_sata_port *port)
{
u32 tmp = BIT(8);
brcm_sata_phy_wr(port, RXPMD_REG_BANK, RXPMD_RX_FREQ_MON_CONTROL1,
~tmp, tmp);
}
static int brcm_sata_phy_calibrate(struct phy *phy)
{
struct brcm_sata_port *port = phy_get_drvdata(phy);
int rc = -EOPNOTSUPP;
switch (port->phy_priv->version) {
case BRCM_SATA_PHY_STB_28NM:
case BRCM_SATA_PHY_STB_40NM:
brcm_stb_sata_calibrate(port);
rc = 0;
break;
default:
break;
}
return rc;
}
static const struct phy_ops phy_ops = {
.init = brcm_sata_phy_init,
.calibrate = brcm_sata_phy_calibrate,
.owner = THIS_MODULE,
};
static const struct of_device_id brcm_sata_phy_of_match[] = {
{ .compatible = "brcm,bcm7216-sata-phy",
.data = (void *)BRCM_SATA_PHY_STB_16NM },
{ .compatible = "brcm,bcm7445-sata-phy",
.data = (void *)BRCM_SATA_PHY_STB_28NM },
{ .compatible = "brcm,bcm7425-sata-phy",
.data = (void *)BRCM_SATA_PHY_STB_40NM },
{ .compatible = "brcm,iproc-ns2-sata-phy",
.data = (void *)BRCM_SATA_PHY_IPROC_NS2 },
{ .compatible = "brcm,iproc-nsp-sata-phy",
.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);
static int brcm_sata_phy_probe(struct platform_device *pdev)
{
const char *rxaeq_mode;
struct device *dev = &pdev->dev;
struct device_node *dn = dev->of_node;
const struct of_device_id *of_id;
struct brcm_sata_phy *priv;
struct phy_provider *provider;
int count = 0;
if (of_get_child_count(dn) == 0)
return -ENODEV;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
dev_set_drvdata(dev, priv);
priv->dev = dev;
priv->phy_base = devm_platform_ioremap_resource_byname(pdev, "phy");
if (IS_ERR(priv->phy_base))
return PTR_ERR(priv->phy_base);
of_id = of_match_node(brcm_sata_phy_of_match, dn);
if (of_id)
priv->version = (uintptr_t)of_id->data;
else
priv->version = BRCM_SATA_PHY_STB_28NM;
if (priv->version == BRCM_SATA_PHY_IPROC_NS2) {
priv->ctrl_base = devm_platform_ioremap_resource_byname(pdev, "phy-ctrl");
if (IS_ERR(priv->ctrl_base))
return PTR_ERR(priv->ctrl_base);
}
for_each_available_child_of_node_scoped(dn, child) {
unsigned int id;
struct brcm_sata_port *port;
if (of_property_read_u32(child, "reg", &id)) {
dev_err(dev, "missing reg property in node %pOFn\n",
child);
return -EINVAL;
}
if (id >= MAX_PORTS) {
dev_err(dev, "invalid reg: %u\n", id);
return -EINVAL;
}
if (priv->phys[id].phy) {
dev_err(dev, "already registered port %u\n", id);
return -EINVAL;
}
port = &priv->phys[id];
port->portnum = id;
port->phy_priv = priv;
port->phy = devm_phy_create(dev, child, &phy_ops);
port->rxaeq_mode = RXAEQ_MODE_OFF;
if (!of_property_read_string(child, "brcm,rxaeq-mode",
&rxaeq_mode))
port->rxaeq_mode = rxaeq_to_val(rxaeq_mode);
if (port->rxaeq_mode == RXAEQ_MODE_MANUAL)
of_property_read_u32(child, "brcm,rxaeq-value",
&port->rxaeq_val);
of_property_read_u32(child, "brcm,tx-amplitude-millivolt",
&port->tx_amplitude_val);
port->ssc_en = of_property_read_bool(child, "brcm,enable-ssc");
if (IS_ERR(port->phy)) {
dev_err(dev, "failed to create PHY\n");
return PTR_ERR(port->phy);
}
phy_set_drvdata(port->phy, port);
count++;
}
provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
if (IS_ERR(provider)) {
dev_err(dev, "could not register PHY provider\n");
return PTR_ERR(provider);
}
dev_dbg(dev, "registered %d port(s)\n", count);
return 0;
}
static struct platform_driver brcm_sata_phy_driver = {
.probe = brcm_sata_phy_probe,
.driver = {
.of_match_table = brcm_sata_phy_of_match,
.name = "brcm-sata-phy",
}
};
module_platform_driver(brcm_sata_phy_driver);
MODULE_DESCRIPTION("Broadcom SATA PHY driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Marc Carino");
MODULE_AUTHOR("Brian Norris");
@@ -0,0 +1,526 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2018, Broadcom */
/*
* This module contains USB PHY initialization for power up and S3 resume
* for newer Synopsys based USB hardware first used on the bcm7216.
*/
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/soc/brcmstb/brcmstb.h>
#include "phy-brcm-usb-init.h"
#define PHY_LOCK_TIMEOUT_MS 200
/* Register definitions for syscon piarbctl registers */
#define PIARBCTL_CAM 0x00
#define PIARBCTL_SPLITTER 0x04
#define PIARBCTL_MISC 0x08
#define PIARBCTL_MISC_SATA_PRIORITY_MASK GENMASK(3, 0)
#define PIARBCTL_MISC_CAM0_MEM_PAGE_MASK GENMASK(7, 4)
#define PIARBCTL_MISC_CAM1_MEM_PAGE_MASK GENMASK(11, 8)
#define PIARBCTL_MISC_USB_MEM_PAGE_MASK GENMASK(15, 12)
#define PIARBCTL_MISC_USB_PRIORITY_MASK GENMASK(19, 16)
#define PIARBCTL_MISC_USB_4G_SDRAM_MASK BIT(29)
#define PIARBCTL_MISC_USB_SELECT_MASK BIT(30)
#define PIARBCTL_MISC_SECURE_MASK BIT(31)
#define PIARBCTL_MISC_USB_ONLY_MASK \
(PIARBCTL_MISC_USB_SELECT_MASK | \
PIARBCTL_MISC_USB_4G_SDRAM_MASK | \
PIARBCTL_MISC_USB_PRIORITY_MASK | \
PIARBCTL_MISC_USB_MEM_PAGE_MASK)
/* Register definitions for the USB CTRL block */
#define USB_CTRL_SETUP 0x00
#define USB_CTRL_SETUP_IOC_MASK BIT(4)
#define USB_CTRL_SETUP_IPP_MASK BIT(5)
#define USB_CTRL_SETUP_SOFT_SHUTDOWN_MASK BIT(9)
#define USB_CTRL_SETUP_SCB1_EN_MASK BIT(14)
#define USB_CTRL_SETUP_SCB2_EN_MASK BIT(15)
#define USB_CTRL_SETUP_tca_drv_sel_MASK BIT(24)
#define USB_CTRL_SETUP_STRAP_IPP_SEL_MASK BIT(25)
#define USB_CTRL_USB_PM 0x04
#define USB_CTRL_USB_PM_REF_S2_CLK_SWITCH_EN_MASK BIT(1)
#define USB_CTRL_USB_PM_UTMI_S2_CLK_SWITCH_EN_MASK BIT(2)
#define USB_CTRL_USB_PM_XHC_S2_CLK_SWITCH_EN_MASK BIT(3)
#define USB_CTRL_USB_PM_XHC_PME_EN_MASK BIT(4)
#define USB_CTRL_USB_PM_XHC_SOFT_RESETB_MASK BIT(22)
#define USB_CTRL_USB_PM_BDC_SOFT_RESETB_MASK BIT(23)
#define USB_CTRL_USB_PM_SOFT_RESET_MASK BIT(30)
#define USB_CTRL_USB_PM_USB_PWRDN_MASK BIT(31)
#define USB_CTRL_USB_PM_STATUS 0x08
#define USB_CTRL_USB_DEVICE_CTL1 0x10
#define USB_CTRL_USB_DEVICE_CTL1_PORT_MODE_MASK GENMASK(1, 0)
#define USB_CTRL_TEST_PORT_CTL 0x30
#define USB_CTRL_TEST_PORT_CTL_TPOUT_SEL_MASK GENMASK(7, 0)
#define USB_CTRL_TEST_PORT_CTL_TPOUT_SEL_PME_GEN_MASK 0x0000002e
#define USB_CTRL_TP_DIAG1 0x34
#define USB_CTLR_TP_DIAG1_wake_MASK BIT(1)
#define USB_CTRL_CTLR_CSHCR 0x50
#define USB_CTRL_CTLR_CSHCR_ctl_pme_en_MASK BIT(18)
#define USB_CTRL_P0_U2PHY_CFG1 0x68
#define USB_CTRL_P0_U2PHY_CFG1_COMMONONN_MASK BIT(10)
#define USB_CTRL_P0_U2PHY_CFG2 0x6c
#define USB_CTRL_P0_U2PHY_CFG2_TXVREFTUNE0_MASK GENMASK(20, 17)
#define USB_CTRL_P0_U2PHY_CFG2_TXVREFTUNE0_SHIFT 17
#define USB_CTRL_P0_U2PHY_CFG2_TXRESTUNE0_MASK GENMASK(24, 23)
#define USB_CTRL_P0_U2PHY_CFG2_TXRESTUNE0_SHIFT 23
#define USB_CTRL_P0_U2PHY_CFG2_TXPREEMPAMPTUNE0_MASK GENMASK(26, 25)
#define USB_CTRL_P0_U2PHY_CFG2_TXPREEMPAMPTUNE0_SHIFT 25
/* Register definitions for the USB_PHY block in 7211b0 */
#define USB_PHY_PLL_CTL 0x00
#define USB_PHY_PLL_CTL_PLL_SUSPEND_MASK BIT(27)
#define USB_PHY_PLL_CTL_PLL_RESETB_MASK BIT(30)
#define USB_PHY_PLL_LDO_CTL 0x08
#define USB_PHY_PLL_LDO_CTL_AFE_BG_PWRDWNB_MASK BIT(0)
#define USB_PHY_PLL_LDO_CTL_AFE_LDO_PWRDWNB_MASK BIT(1)
#define USB_PHY_PLL_LDO_CTL_AFE_CORERDY_MASK BIT(2)
#define USB_PHY_UTMI_CTL_1 0x04
#define USB_PHY_UTMI_CTL_1_PHY_MODE_MASK GENMASK(3, 2)
#define USB_PHY_UTMI_CTL_1_PHY_MODE_SHIFT 2
#define USB_PHY_UTMI_CTL_1_POWER_UP_FSM_EN_MASK BIT(11)
#define USB_PHY_IDDQ 0x1c
#define USB_PHY_IDDQ_phy_iddq_MASK BIT(0)
#define USB_PHY_STATUS 0x20
#define USB_PHY_STATUS_pll_lock_MASK BIT(0)
/* Register definitions for the MDIO registers in the DWC2 block of
* the 7211b0.
* NOTE: The PHY's MDIO registers are only accessible through the
* legacy DesignWare USB controller even though it's not being used.
*/
#define USB_GMDIOCSR 0
#define USB_GMDIOGEN 4
/* Register definitions for the BDC EC block in 7211b0 */
#define BDC_EC_AXIRDA 0x0c
#define BDC_EC_AXIRDA_RTS_MASK GENMASK(31, 28)
#define BDC_EC_AXIRDA_RTS_SHIFT 28
#define USB_XHCI_GBL_GUSB2PHYCFG 0x100
#define USB_XHCI_GBL_GUSB2PHYCFG_U2_FREECLK_EXISTS_MASK BIT(30)
static void usb_mdio_write_7211b0(struct brcm_usb_init_params *params,
uint8_t addr, uint16_t data)
{
void __iomem *usb_mdio = params->regs[BRCM_REGS_USB_MDIO];
addr &= 0x1f; /* 5-bit address */
brcm_usb_writel(0xffffffff, usb_mdio + USB_GMDIOGEN);
while (brcm_usb_readl(usb_mdio + USB_GMDIOCSR) & (1<<31))
;
brcm_usb_writel(0x59020000 | (addr << 18) | data,
usb_mdio + USB_GMDIOGEN);
while (brcm_usb_readl(usb_mdio + USB_GMDIOCSR) & (1<<31))
;
brcm_usb_writel(0x00000000, usb_mdio + USB_GMDIOGEN);
while (brcm_usb_readl(usb_mdio + USB_GMDIOCSR) & (1<<31))
;
}
static uint16_t __maybe_unused usb_mdio_read_7211b0(
struct brcm_usb_init_params *params, uint8_t addr)
{
void __iomem *usb_mdio = params->regs[BRCM_REGS_USB_MDIO];
addr &= 0x1f; /* 5-bit address */
brcm_usb_writel(0xffffffff, usb_mdio + USB_GMDIOGEN);
while (brcm_usb_readl(usb_mdio + USB_GMDIOCSR) & (1<<31))
;
brcm_usb_writel(0x69020000 | (addr << 18), usb_mdio + USB_GMDIOGEN);
while (brcm_usb_readl(usb_mdio + USB_GMDIOCSR) & (1<<31))
;
brcm_usb_writel(0x00000000, usb_mdio + USB_GMDIOGEN);
while (brcm_usb_readl(usb_mdio + USB_GMDIOCSR) & (1<<31))
;
return brcm_usb_readl(usb_mdio + USB_GMDIOCSR) & 0xffff;
}
static void usb2_eye_fix_7211b0(struct brcm_usb_init_params *params)
{
/* select bank */
usb_mdio_write_7211b0(params, 0x1f, 0x80a0);
/* Set the eye */
usb_mdio_write_7211b0(params, 0x0a, 0xc6a0);
}
static void xhci_soft_reset(struct brcm_usb_init_params *params,
int on_off)
{
void __iomem *ctrl = params->regs[BRCM_REGS_CTRL];
void __iomem *xhci_gbl = params->regs[BRCM_REGS_XHCI_GBL];
/* Assert reset */
if (on_off) {
USB_CTRL_UNSET(ctrl, USB_PM, XHC_SOFT_RESETB);
/* De-assert reset */
} else {
USB_CTRL_SET(ctrl, USB_PM, XHC_SOFT_RESETB);
/* Required for COMMONONN to be set */
if (params->supported_port_modes != USB_CTLR_MODE_DRD)
USB_XHCI_GBL_UNSET(xhci_gbl, GUSB2PHYCFG,
U2_FREECLK_EXISTS);
}
}
static void usb_init_ipp(struct brcm_usb_init_params *params)
{
void __iomem *ctrl = params->regs[BRCM_REGS_CTRL];
u32 reg;
u32 orig_reg;
pr_debug("%s\n", __func__);
orig_reg = reg = brcm_usb_readl(USB_CTRL_REG(ctrl, SETUP));
if (params->ipp != 2)
/* override ipp strap pin (if it exits) */
reg &= ~(USB_CTRL_MASK(SETUP, STRAP_IPP_SEL));
/* Override the default OC and PP polarity */
reg &= ~(USB_CTRL_MASK(SETUP, IPP) | USB_CTRL_MASK(SETUP, IOC));
if (params->ioc)
reg |= USB_CTRL_MASK(SETUP, IOC);
if (params->ipp == 1)
reg |= USB_CTRL_MASK(SETUP, IPP);
brcm_usb_writel(reg, USB_CTRL_REG(ctrl, SETUP));
/*
* If we're changing IPP, make sure power is off long enough
* to turn off any connected devices.
*/
if ((reg ^ orig_reg) & USB_CTRL_MASK(SETUP, IPP))
msleep(50);
}
static void syscon_piarbctl_init(struct regmap *rmap)
{
/* Switch from legacy USB OTG controller to new STB USB controller */
regmap_update_bits(rmap, PIARBCTL_MISC, PIARBCTL_MISC_USB_ONLY_MASK,
PIARBCTL_MISC_USB_SELECT_MASK |
PIARBCTL_MISC_USB_4G_SDRAM_MASK);
}
static void usb_init_common(struct brcm_usb_init_params *params)
{
u32 reg;
void __iomem *ctrl = params->regs[BRCM_REGS_CTRL];
pr_debug("%s\n", __func__);
if (USB_CTRL_MASK(USB_DEVICE_CTL1, PORT_MODE)) {
reg = brcm_usb_readl(USB_CTRL_REG(ctrl, USB_DEVICE_CTL1));
reg &= ~USB_CTRL_MASK(USB_DEVICE_CTL1, PORT_MODE);
reg |= params->port_mode;
brcm_usb_writel(reg, USB_CTRL_REG(ctrl, USB_DEVICE_CTL1));
}
switch (params->supported_port_modes) {
case USB_CTLR_MODE_HOST:
USB_CTRL_UNSET(ctrl, USB_PM, BDC_SOFT_RESETB);
break;
default:
USB_CTRL_UNSET(ctrl, USB_PM, BDC_SOFT_RESETB);
USB_CTRL_SET(ctrl, USB_PM, BDC_SOFT_RESETB);
break;
}
}
static void usb_wake_enable_7211b0(struct brcm_usb_init_params *params,
bool enable)
{
void __iomem *ctrl = params->regs[BRCM_REGS_CTRL];
if (enable)
USB_CTRL_SET(ctrl, CTLR_CSHCR, ctl_pme_en);
else
USB_CTRL_UNSET(ctrl, CTLR_CSHCR, ctl_pme_en);
}
static void usb_wake_enable_7216(struct brcm_usb_init_params *params,
bool enable)
{
void __iomem *ctrl = params->regs[BRCM_REGS_CTRL];
if (enable)
USB_CTRL_SET(ctrl, USB_PM, XHC_PME_EN);
else
USB_CTRL_UNSET(ctrl, USB_PM, XHC_PME_EN);
}
static void usb_init_common_7211b0(struct brcm_usb_init_params *params)
{
void __iomem *ctrl = params->regs[BRCM_REGS_CTRL];
void __iomem *usb_phy = params->regs[BRCM_REGS_USB_PHY];
void __iomem *bdc_ec = params->regs[BRCM_REGS_BDC_EC];
int timeout_ms = PHY_LOCK_TIMEOUT_MS;
u32 reg;
if (params->syscon_piarbctl)
syscon_piarbctl_init(params->syscon_piarbctl);
USB_CTRL_UNSET(ctrl, USB_PM, USB_PWRDN);
usb_wake_enable_7211b0(params, false);
if (!params->wake_enabled) {
/* undo possible suspend settings */
brcm_usb_writel(0, usb_phy + USB_PHY_IDDQ);
reg = brcm_usb_readl(usb_phy + USB_PHY_PLL_CTL);
reg |= USB_PHY_PLL_CTL_PLL_RESETB_MASK;
brcm_usb_writel(reg, usb_phy + USB_PHY_PLL_CTL);
/* temporarily enable FSM so PHY comes up properly */
reg = brcm_usb_readl(usb_phy + USB_PHY_UTMI_CTL_1);
reg |= USB_PHY_UTMI_CTL_1_POWER_UP_FSM_EN_MASK;
brcm_usb_writel(reg, usb_phy + USB_PHY_UTMI_CTL_1);
}
/* Disable PLL auto suspend */
reg = brcm_usb_readl(usb_phy + USB_PHY_PLL_CTL);
reg |= USB_PHY_PLL_CTL_PLL_SUSPEND_MASK;
brcm_usb_writel(reg, usb_phy + USB_PHY_PLL_CTL);
/* Init the PHY */
reg = USB_PHY_PLL_LDO_CTL_AFE_CORERDY_MASK |
USB_PHY_PLL_LDO_CTL_AFE_LDO_PWRDWNB_MASK |
USB_PHY_PLL_LDO_CTL_AFE_BG_PWRDWNB_MASK;
brcm_usb_writel(reg, usb_phy + USB_PHY_PLL_LDO_CTL);
/* wait for lock */
while (timeout_ms-- > 0) {
reg = brcm_usb_readl(usb_phy + USB_PHY_STATUS);
if (reg & USB_PHY_STATUS_pll_lock_MASK)
break;
usleep_range(1000, 2000);
}
/* Set the PHY_MODE */
reg = brcm_usb_readl(usb_phy + USB_PHY_UTMI_CTL_1);
reg &= ~USB_PHY_UTMI_CTL_1_PHY_MODE_MASK;
reg |= params->supported_port_modes << USB_PHY_UTMI_CTL_1_PHY_MODE_SHIFT;
brcm_usb_writel(reg, usb_phy + USB_PHY_UTMI_CTL_1);
usb_init_common(params);
/*
* The BDC controller will get occasional failures with
* the default "Read Transaction Size" of 6 (1024 bytes).
* Set it to 4 (256 bytes).
*/
if ((params->supported_port_modes != USB_CTLR_MODE_HOST) && bdc_ec) {
reg = brcm_usb_readl(bdc_ec + BDC_EC_AXIRDA);
reg &= ~BDC_EC_AXIRDA_RTS_MASK;
reg |= (0x4 << BDC_EC_AXIRDA_RTS_SHIFT);
brcm_usb_writel(reg, bdc_ec + BDC_EC_AXIRDA);
}
/*
* Disable FSM, otherwise the PHY will auto suspend when no
* device is connected and will be reset on resume.
*/
reg = brcm_usb_readl(usb_phy + USB_PHY_UTMI_CTL_1);
reg &= ~USB_PHY_UTMI_CTL_1_POWER_UP_FSM_EN_MASK;
brcm_usb_writel(reg, usb_phy + USB_PHY_UTMI_CTL_1);
usb2_eye_fix_7211b0(params);
}
static void usb_init_common_7216(struct brcm_usb_init_params *params)
{
void __iomem *ctrl = params->regs[BRCM_REGS_CTRL];
USB_CTRL_UNSET(ctrl, USB_PM, XHC_S2_CLK_SWITCH_EN);
/*
* The PHY might be in a bad state if it is already powered
* up. Toggle the power just in case.
*/
USB_CTRL_SET(ctrl, USB_PM, USB_PWRDN);
USB_CTRL_UNSET(ctrl, USB_PM, USB_PWRDN);
/* 1 millisecond - for USB clocks to settle down */
usleep_range(1000, 2000);
/*
* Disable PHY when port is suspended
* Does not work in DRD mode
*/
if (params->supported_port_modes != USB_CTLR_MODE_DRD)
USB_CTRL_SET(ctrl, P0_U2PHY_CFG1, COMMONONN);
usb_wake_enable_7216(params, false);
usb_init_common(params);
}
static void usb_init_xhci(struct brcm_usb_init_params *params)
{
pr_debug("%s\n", __func__);
xhci_soft_reset(params, 0);
}
static void usb_uninit_common_7216(struct brcm_usb_init_params *params)
{
void __iomem *ctrl = params->regs[BRCM_REGS_CTRL];
pr_debug("%s\n", __func__);
if (params->wake_enabled) {
/* Switch to using slower clock during suspend to save power */
USB_CTRL_SET(ctrl, USB_PM, XHC_S2_CLK_SWITCH_EN);
usb_wake_enable_7216(params, true);
} else {
USB_CTRL_SET(ctrl, USB_PM, USB_PWRDN);
}
}
static void usb_init_common_74110(struct brcm_usb_init_params *params)
{
void __iomem *ctrl = params->regs[BRCM_REGS_CTRL];
u32 reg;
reg = brcm_usb_readl(USB_CTRL_REG(ctrl, USB_PM));
reg &= ~(USB_CTRL_MASK(USB_PM, REF_S2_CLK_SWITCH_EN) |
USB_CTRL_MASK(USB_PM, UTMI_S2_CLK_SWITCH_EN));
brcm_usb_writel(reg, USB_CTRL_REG(ctrl, USB_PM));
usb_init_common_7216(params);
reg = brcm_usb_readl(USB_CTRL_REG(ctrl, P0_U2PHY_CFG2));
reg &= ~(USB_CTRL_P0_U2PHY_CFG2_TXVREFTUNE0_MASK |
USB_CTRL_P0_U2PHY_CFG2_TXRESTUNE0_MASK |
USB_CTRL_P0_U2PHY_CFG2_TXPREEMPAMPTUNE0_MASK);
reg |= (0x6 << USB_CTRL_P0_U2PHY_CFG2_TXVREFTUNE0_SHIFT) |
(0x3 << USB_CTRL_P0_U2PHY_CFG2_TXRESTUNE0_SHIFT) |
(0x2 << USB_CTRL_P0_U2PHY_CFG2_TXPREEMPAMPTUNE0_SHIFT);
brcm_usb_writel(reg, USB_CTRL_REG(ctrl, P0_U2PHY_CFG2));
}
static void usb_uninit_common_74110(struct brcm_usb_init_params *params)
{
void __iomem *ctrl = params->regs[BRCM_REGS_CTRL];
u32 reg;
if (params->wake_enabled) {
reg = brcm_usb_readl(USB_CTRL_REG(ctrl, USB_PM));
reg |= (USB_CTRL_MASK(USB_PM, REF_S2_CLK_SWITCH_EN) |
USB_CTRL_MASK(USB_PM, UTMI_S2_CLK_SWITCH_EN));
brcm_usb_writel(reg, USB_CTRL_REG(ctrl, USB_PM));
}
usb_uninit_common_7216(params);
}
static void usb_uninit_common_7211b0(struct brcm_usb_init_params *params)
{
void __iomem *ctrl = params->regs[BRCM_REGS_CTRL];
void __iomem *usb_phy = params->regs[BRCM_REGS_USB_PHY];
u32 reg;
pr_debug("%s\n", __func__);
if (params->wake_enabled) {
USB_CTRL_SET(ctrl, TEST_PORT_CTL, TPOUT_SEL_PME_GEN);
usb_wake_enable_7211b0(params, true);
} else {
USB_CTRL_SET(ctrl, USB_PM, USB_PWRDN);
brcm_usb_writel(0, usb_phy + USB_PHY_PLL_LDO_CTL);
reg = brcm_usb_readl(usb_phy + USB_PHY_PLL_CTL);
reg &= ~USB_PHY_PLL_CTL_PLL_RESETB_MASK;
brcm_usb_writel(reg, usb_phy + USB_PHY_PLL_CTL);
brcm_usb_writel(USB_PHY_IDDQ_phy_iddq_MASK,
usb_phy + USB_PHY_IDDQ);
}
}
static void usb_uninit_xhci(struct brcm_usb_init_params *params)
{
pr_debug("%s\n", __func__);
if (!params->wake_enabled)
xhci_soft_reset(params, 1);
}
static int usb_get_dual_select(struct brcm_usb_init_params *params)
{
void __iomem *ctrl = params->regs[BRCM_REGS_CTRL];
u32 reg = 0;
pr_debug("%s\n", __func__);
reg = brcm_usb_readl(USB_CTRL_REG(ctrl, USB_DEVICE_CTL1));
reg &= USB_CTRL_MASK(USB_DEVICE_CTL1, PORT_MODE);
return reg;
}
static void usb_set_dual_select(struct brcm_usb_init_params *params)
{
void __iomem *ctrl = params->regs[BRCM_REGS_CTRL];
u32 reg;
pr_debug("%s\n", __func__);
reg = brcm_usb_readl(USB_CTRL_REG(ctrl, USB_DEVICE_CTL1));
reg &= ~USB_CTRL_MASK(USB_DEVICE_CTL1, PORT_MODE);
reg |= params->port_mode;
brcm_usb_writel(reg, USB_CTRL_REG(ctrl, USB_DEVICE_CTL1));
}
static const struct brcm_usb_init_ops bcm74110_ops = {
.init_ipp = usb_init_ipp,
.init_common = usb_init_common_74110,
.init_xhci = usb_init_xhci,
.uninit_common = usb_uninit_common_74110,
.uninit_xhci = usb_uninit_xhci,
.get_dual_select = usb_get_dual_select,
.set_dual_select = usb_set_dual_select,
};
static const struct brcm_usb_init_ops bcm7216_ops = {
.init_ipp = usb_init_ipp,
.init_common = usb_init_common_7216,
.init_xhci = usb_init_xhci,
.uninit_common = usb_uninit_common_7216,
.uninit_xhci = usb_uninit_xhci,
.get_dual_select = usb_get_dual_select,
.set_dual_select = usb_set_dual_select,
};
static const struct brcm_usb_init_ops bcm7211b0_ops = {
.init_ipp = usb_init_ipp,
.init_common = usb_init_common_7211b0,
.init_xhci = usb_init_xhci,
.uninit_common = usb_uninit_common_7211b0,
.uninit_xhci = usb_uninit_xhci,
.get_dual_select = usb_get_dual_select,
.set_dual_select = usb_set_dual_select,
};
void brcm_usb_dvr_init_74110(struct brcm_usb_init_params *params)
{
params->family_name = "74110";
params->ops = &bcm74110_ops;
}
void brcm_usb_dvr_init_7216(struct brcm_usb_init_params *params)
{
pr_debug("%s\n", __func__);
params->family_name = "7216";
params->ops = &bcm7216_ops;
}
void brcm_usb_dvr_init_7211b0(struct brcm_usb_init_params *params)
{
pr_debug("%s\n", __func__);
params->family_name = "7211";
params->ops = &bcm7211b0_ops;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,171 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2014-2017 Broadcom
*/
#ifndef _USB_BRCM_COMMON_INIT_H
#define _USB_BRCM_COMMON_INIT_H
#include <linux/regmap.h>
#define USB_CTLR_MODE_HOST 0
#define USB_CTLR_MODE_DEVICE 1
#define USB_CTLR_MODE_DRD 2
#define USB_CTLR_MODE_TYPEC_PD 3
enum brcmusb_reg_sel {
BRCM_REGS_CTRL = 0,
BRCM_REGS_XHCI_EC,
BRCM_REGS_XHCI_GBL,
BRCM_REGS_USB_PHY,
BRCM_REGS_USB_MDIO,
BRCM_REGS_BDC_EC,
BRCM_REGS_MAX
};
#define USB_CTRL_REG(base, reg) ((void __iomem *)base + USB_CTRL_##reg)
#define USB_XHCI_EC_REG(base, reg) ((void __iomem *)base + USB_XHCI_EC_##reg)
#define USB_CTRL_MASK(reg, field) \
USB_CTRL_##reg##_##field##_MASK
#define USB_CTRL_SET(base, reg, field) \
brcm_usb_ctrl_set(USB_CTRL_REG(base, reg), \
USB_CTRL_##reg##_##field##_MASK)
#define USB_CTRL_UNSET(base, reg, field) \
brcm_usb_ctrl_unset(USB_CTRL_REG(base, reg), \
USB_CTRL_##reg##_##field##_MASK)
#define USB_XHCI_GBL_REG(base, reg) ((void __iomem *)base + USB_XHCI_GBL_##reg)
#define USB_XHCI_GBL_SET(base, reg, field) \
brcm_usb_ctrl_set(USB_XHCI_GBL_REG(base, reg), \
USB_XHCI_GBL_##reg##_##field##_MASK)
#define USB_XHCI_GBL_UNSET(base, reg, field) \
brcm_usb_ctrl_unset(USB_XHCI_GBL_REG(base, reg), \
USB_XHCI_GBL_##reg##_##field##_MASK)
struct brcm_usb_init_params;
struct brcm_usb_init_ops {
void (*init_ipp)(struct brcm_usb_init_params *params);
void (*init_common)(struct brcm_usb_init_params *params);
void (*init_eohci)(struct brcm_usb_init_params *params);
void (*init_xhci)(struct brcm_usb_init_params *params);
void (*uninit_common)(struct brcm_usb_init_params *params);
void (*uninit_eohci)(struct brcm_usb_init_params *params);
void (*uninit_xhci)(struct brcm_usb_init_params *params);
int (*get_dual_select)(struct brcm_usb_init_params *params);
void (*set_dual_select)(struct brcm_usb_init_params *params);
};
struct brcm_usb_init_params {
void __iomem *regs[BRCM_REGS_MAX];
int ioc;
int ipp;
int supported_port_modes;
int port_mode;
u32 family_id;
u32 product_id;
int selected_family;
const char *family_name;
const u32 *usb_reg_bits_map;
const struct brcm_usb_init_ops *ops;
struct regmap *syscon_piarbctl;
bool wake_enabled;
};
void brcm_usb_dvr_init_74110(struct brcm_usb_init_params *params);
void brcm_usb_dvr_init_4908(struct brcm_usb_init_params *params);
void brcm_usb_dvr_init_7445(struct brcm_usb_init_params *params);
void brcm_usb_dvr_init_7216(struct brcm_usb_init_params *params);
void brcm_usb_dvr_init_7211b0(struct brcm_usb_init_params *params);
static inline u32 brcm_usb_readl(void __iomem *addr)
{
/*
* MIPS endianness is configured by boot strap, which also reverses all
* bus endianness (i.e., big-endian CPU + big endian bus ==> native
* endian I/O).
*
* Other architectures (e.g., ARM) either do not support big endian, or
* else leave I/O in little endian mode.
*/
if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
return __raw_readl(addr);
else
return readl_relaxed(addr);
}
static inline void brcm_usb_writel(u32 val, void __iomem *addr)
{
/* See brcmnand_readl() comments */
if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
__raw_writel(val, addr);
else
writel_relaxed(val, addr);
}
static inline void brcm_usb_ctrl_unset(void __iomem *reg, u32 mask)
{
brcm_usb_writel(brcm_usb_readl(reg) & ~(mask), reg);
};
static inline void brcm_usb_ctrl_set(void __iomem *reg, u32 mask)
{
brcm_usb_writel(brcm_usb_readl(reg) | (mask), reg);
};
static inline void brcm_usb_init_ipp(struct brcm_usb_init_params *ini)
{
if (ini->ops->init_ipp)
ini->ops->init_ipp(ini);
}
static inline void brcm_usb_init_common(struct brcm_usb_init_params *ini)
{
if (ini->ops->init_common)
ini->ops->init_common(ini);
}
static inline void brcm_usb_init_eohci(struct brcm_usb_init_params *ini)
{
if (ini->ops->init_eohci)
ini->ops->init_eohci(ini);
}
static inline void brcm_usb_init_xhci(struct brcm_usb_init_params *ini)
{
if (ini->ops->init_xhci)
ini->ops->init_xhci(ini);
}
static inline void brcm_usb_uninit_common(struct brcm_usb_init_params *ini)
{
if (ini->ops->uninit_common)
ini->ops->uninit_common(ini);
}
static inline void brcm_usb_uninit_eohci(struct brcm_usb_init_params *ini)
{
if (ini->ops->uninit_eohci)
ini->ops->uninit_eohci(ini);
}
static inline void brcm_usb_uninit_xhci(struct brcm_usb_init_params *ini)
{
if (ini->ops->uninit_xhci)
ini->ops->uninit_xhci(ini);
}
static inline int brcm_usb_get_dual_select(struct brcm_usb_init_params *ini)
{
if (ini->ops->get_dual_select)
return ini->ops->get_dual_select(ini);
return 0;
}
static inline void brcm_usb_set_dual_select(struct brcm_usb_init_params *ini)
{
if (ini->ops->set_dual_select)
ini->ops->set_dual_select(ini);
}
#endif /* _USB_BRCM_COMMON_INIT_H */
@@ -0,0 +1,696 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* phy-brcm-usb.c - Broadcom USB Phy Driver
*
* Copyright (C) 2015-2017 Broadcom
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/soc/brcmstb/brcmstb.h>
#include <dt-bindings/phy/phy.h>
#include <linux/mfd/syscon.h>
#include <linux/suspend.h>
#include "phy-brcm-usb-init.h"
static DEFINE_MUTEX(sysfs_lock);
enum brcm_usb_phy_id {
BRCM_USB_PHY_2_0 = 0,
BRCM_USB_PHY_3_0,
BRCM_USB_PHY_ID_MAX
};
struct value_to_name_map {
int value;
const char *name;
};
struct match_chip_info {
void (*init_func)(struct brcm_usb_init_params *params);
u8 required_regs[BRCM_REGS_MAX + 1];
u8 optional_reg;
};
static const struct value_to_name_map brcm_dr_mode_to_name[] = {
{ USB_CTLR_MODE_HOST, "host" },
{ USB_CTLR_MODE_DEVICE, "peripheral" },
{ USB_CTLR_MODE_DRD, "drd" },
{ USB_CTLR_MODE_TYPEC_PD, "typec-pd" }
};
static const struct value_to_name_map brcm_dual_mode_to_name[] = {
{ 0, "host" },
{ 1, "device" },
{ 2, "auto" },
};
struct brcm_usb_phy {
struct phy *phy;
unsigned int id;
bool inited;
};
struct brcm_usb_phy_data {
struct brcm_usb_init_params ini;
bool has_eohci;
bool has_xhci;
struct clk *usb_20_clk;
struct clk *usb_30_clk;
struct clk *suspend_clk;
struct mutex mutex; /* serialize phy init */
int init_count;
int wake_irq;
struct brcm_usb_phy phys[BRCM_USB_PHY_ID_MAX];
struct notifier_block pm_notifier;
bool pm_active;
};
static s8 *node_reg_names[BRCM_REGS_MAX] = {
"crtl", "xhci_ec", "xhci_gbl", "usb_phy", "usb_mdio", "bdc_ec"
};
static int brcm_pm_notifier(struct notifier_block *notifier,
unsigned long pm_event,
void *unused)
{
struct brcm_usb_phy_data *priv =
container_of(notifier, struct brcm_usb_phy_data, pm_notifier);
switch (pm_event) {
case PM_HIBERNATION_PREPARE:
case PM_SUSPEND_PREPARE:
priv->pm_active = true;
break;
case PM_POST_RESTORE:
case PM_POST_HIBERNATION:
case PM_POST_SUSPEND:
priv->pm_active = false;
break;
}
return NOTIFY_DONE;
}
static irqreturn_t brcm_usb_phy_wake_isr(int irq, void *dev_id)
{
struct device *dev = dev_id;
pm_wakeup_event(dev, 0);
return IRQ_HANDLED;
}
static int brcm_usb_phy_init(struct phy *gphy)
{
struct brcm_usb_phy *phy = phy_get_drvdata(gphy);
struct brcm_usb_phy_data *priv =
container_of(phy, struct brcm_usb_phy_data, phys[phy->id]);
if (priv->pm_active)
return 0;
/*
* Use a lock to make sure a second caller waits until
* the base phy is inited before using it.
*/
mutex_lock(&priv->mutex);
if (priv->init_count++ == 0) {
clk_prepare_enable(priv->usb_20_clk);
clk_prepare_enable(priv->usb_30_clk);
clk_prepare_enable(priv->suspend_clk);
brcm_usb_init_common(&priv->ini);
}
mutex_unlock(&priv->mutex);
if (phy->id == BRCM_USB_PHY_2_0)
brcm_usb_init_eohci(&priv->ini);
else if (phy->id == BRCM_USB_PHY_3_0)
brcm_usb_init_xhci(&priv->ini);
phy->inited = true;
dev_dbg(&gphy->dev, "INIT, id: %d, total: %d\n", phy->id,
priv->init_count);
return 0;
}
static int brcm_usb_phy_exit(struct phy *gphy)
{
struct brcm_usb_phy *phy = phy_get_drvdata(gphy);
struct brcm_usb_phy_data *priv =
container_of(phy, struct brcm_usb_phy_data, phys[phy->id]);
if (priv->pm_active)
return 0;
dev_dbg(&gphy->dev, "EXIT\n");
if (phy->id == BRCM_USB_PHY_2_0)
brcm_usb_uninit_eohci(&priv->ini);
if (phy->id == BRCM_USB_PHY_3_0)
brcm_usb_uninit_xhci(&priv->ini);
/* If both xhci and eohci are gone, reset everything else */
mutex_lock(&priv->mutex);
if (--priv->init_count == 0) {
brcm_usb_uninit_common(&priv->ini);
clk_disable_unprepare(priv->usb_20_clk);
clk_disable_unprepare(priv->usb_30_clk);
clk_disable_unprepare(priv->suspend_clk);
}
mutex_unlock(&priv->mutex);
phy->inited = false;
return 0;
}
static const struct phy_ops brcm_usb_phy_ops = {
.init = brcm_usb_phy_init,
.exit = brcm_usb_phy_exit,
.owner = THIS_MODULE,
};
static struct phy *brcm_usb_phy_xlate(struct device *dev,
const struct of_phandle_args *args)
{
struct brcm_usb_phy_data *data = dev_get_drvdata(dev);
/*
* values 0 and 1 are for backward compatibility with
* device tree nodes from older bootloaders.
*/
switch (args->args[0]) {
case 0:
case PHY_TYPE_USB2:
if (data->phys[BRCM_USB_PHY_2_0].phy)
return data->phys[BRCM_USB_PHY_2_0].phy;
dev_warn(dev, "Error, 2.0 Phy not found\n");
break;
case 1:
case PHY_TYPE_USB3:
if (data->phys[BRCM_USB_PHY_3_0].phy)
return data->phys[BRCM_USB_PHY_3_0].phy;
dev_warn(dev, "Error, 3.0 Phy not found\n");
break;
}
return ERR_PTR(-ENODEV);
}
static int name_to_value(const struct value_to_name_map *table, int count,
const char *name, int *value)
{
int x;
*value = 0;
for (x = 0; x < count; x++) {
if (sysfs_streq(name, table[x].name)) {
*value = x;
return 0;
}
}
return -EINVAL;
}
static const char *value_to_name(const struct value_to_name_map *table, int count,
int value)
{
if (value >= count)
return "unknown";
return table[value].name;
}
static ssize_t dr_mode_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct brcm_usb_phy_data *priv = dev_get_drvdata(dev);
return sprintf(buf, "%s\n",
value_to_name(&brcm_dr_mode_to_name[0],
ARRAY_SIZE(brcm_dr_mode_to_name),
priv->ini.supported_port_modes));
}
static DEVICE_ATTR_RO(dr_mode);
static ssize_t dual_select_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct brcm_usb_phy_data *priv = dev_get_drvdata(dev);
int value;
int res;
mutex_lock(&sysfs_lock);
res = name_to_value(&brcm_dual_mode_to_name[0],
ARRAY_SIZE(brcm_dual_mode_to_name), buf, &value);
if (!res) {
priv->ini.port_mode = value;
brcm_usb_set_dual_select(&priv->ini);
res = len;
}
mutex_unlock(&sysfs_lock);
return res;
}
static ssize_t dual_select_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct brcm_usb_phy_data *priv = dev_get_drvdata(dev);
int value;
mutex_lock(&sysfs_lock);
value = brcm_usb_get_dual_select(&priv->ini);
mutex_unlock(&sysfs_lock);
return sprintf(buf, "%s\n",
value_to_name(&brcm_dual_mode_to_name[0],
ARRAY_SIZE(brcm_dual_mode_to_name),
value));
}
static DEVICE_ATTR_RW(dual_select);
static struct attribute *brcm_usb_phy_attrs[] = {
&dev_attr_dr_mode.attr,
&dev_attr_dual_select.attr,
NULL
};
static const struct attribute_group brcm_usb_phy_group = {
.attrs = brcm_usb_phy_attrs,
};
static const struct match_chip_info chip_info_74110 = {
.init_func = &brcm_usb_dvr_init_74110,
.required_regs = {
BRCM_REGS_CTRL,
BRCM_REGS_XHCI_EC,
BRCM_REGS_XHCI_GBL,
-1,
},
};
static const struct match_chip_info chip_info_4908 = {
.init_func = &brcm_usb_dvr_init_4908,
.required_regs = {
BRCM_REGS_CTRL,
BRCM_REGS_XHCI_EC,
-1,
},
};
static const struct match_chip_info chip_info_7216 = {
.init_func = &brcm_usb_dvr_init_7216,
.required_regs = {
BRCM_REGS_CTRL,
BRCM_REGS_XHCI_EC,
BRCM_REGS_XHCI_GBL,
-1,
},
};
static const struct match_chip_info chip_info_7211b0 = {
.init_func = &brcm_usb_dvr_init_7211b0,
.required_regs = {
BRCM_REGS_CTRL,
BRCM_REGS_XHCI_EC,
BRCM_REGS_XHCI_GBL,
BRCM_REGS_USB_PHY,
BRCM_REGS_USB_MDIO,
-1,
},
.optional_reg = BRCM_REGS_BDC_EC,
};
static const struct match_chip_info chip_info_7445 = {
.init_func = &brcm_usb_dvr_init_7445,
.required_regs = {
BRCM_REGS_CTRL,
BRCM_REGS_XHCI_EC,
-1,
},
};
static const struct of_device_id brcm_usb_dt_ids[] = {
{
.compatible = "brcm,bcm74110-usb-phy",
.data = &chip_info_74110,
},
{
.compatible = "brcm,bcm4908-usb-phy",
.data = &chip_info_4908,
},
{
.compatible = "brcm,bcm7216-usb-phy",
.data = &chip_info_7216,
},
{
.compatible = "brcm,bcm7211-usb-phy",
.data = &chip_info_7211b0,
},
{
.compatible = "brcm,brcmstb-usb-phy",
.data = &chip_info_7445,
},
{ /* sentinel */ }
};
static int brcm_usb_get_regs(struct platform_device *pdev,
enum brcmusb_reg_sel regs,
struct brcm_usb_init_params *ini,
bool optional)
{
struct resource *res;
/* Older DT nodes have ctrl and optional xhci_ec by index only */
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
node_reg_names[regs]);
if (res == NULL) {
if (regs == BRCM_REGS_CTRL) {
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
} else if (regs == BRCM_REGS_XHCI_EC) {
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
/* XHCI_EC registers are optional */
if (res == NULL)
return 0;
}
if (res == NULL) {
if (optional) {
dev_dbg(&pdev->dev,
"Optional reg %s not found\n",
node_reg_names[regs]);
return 0;
}
dev_err(&pdev->dev, "can't get %s base addr\n",
node_reg_names[regs]);
return 1;
}
}
ini->regs[regs] = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(ini->regs[regs])) {
dev_err(&pdev->dev, "can't map %s register space\n",
node_reg_names[regs]);
return 1;
}
return 0;
}
static int brcm_usb_phy_dvr_init(struct platform_device *pdev,
struct brcm_usb_phy_data *priv,
struct device_node *dn)
{
struct device *dev = &pdev->dev;
struct phy *gphy = NULL;
int err;
priv->usb_20_clk = of_clk_get_by_name(dn, "sw_usb");
if (IS_ERR(priv->usb_20_clk)) {
if (PTR_ERR(priv->usb_20_clk) == -EPROBE_DEFER)
return -EPROBE_DEFER;
dev_info(dev, "Clock not found in Device Tree\n");
priv->usb_20_clk = NULL;
}
err = clk_prepare_enable(priv->usb_20_clk);
if (err)
return err;
if (priv->has_eohci) {
gphy = devm_phy_create(dev, NULL, &brcm_usb_phy_ops);
if (IS_ERR(gphy)) {
dev_err(dev, "failed to create EHCI/OHCI PHY\n");
return PTR_ERR(gphy);
}
priv->phys[BRCM_USB_PHY_2_0].phy = gphy;
priv->phys[BRCM_USB_PHY_2_0].id = BRCM_USB_PHY_2_0;
phy_set_drvdata(gphy, &priv->phys[BRCM_USB_PHY_2_0]);
}
if (priv->has_xhci) {
gphy = devm_phy_create(dev, NULL, &brcm_usb_phy_ops);
if (IS_ERR(gphy)) {
dev_err(dev, "failed to create XHCI PHY\n");
return PTR_ERR(gphy);
}
priv->phys[BRCM_USB_PHY_3_0].phy = gphy;
priv->phys[BRCM_USB_PHY_3_0].id = BRCM_USB_PHY_3_0;
phy_set_drvdata(gphy, &priv->phys[BRCM_USB_PHY_3_0]);
priv->usb_30_clk = of_clk_get_by_name(dn, "sw_usb3");
if (IS_ERR(priv->usb_30_clk)) {
if (PTR_ERR(priv->usb_30_clk) == -EPROBE_DEFER)
return -EPROBE_DEFER;
dev_info(dev,
"USB3.0 clock not found in Device Tree\n");
priv->usb_30_clk = NULL;
}
err = clk_prepare_enable(priv->usb_30_clk);
if (err)
return err;
}
priv->suspend_clk = clk_get(dev, "usb0_freerun");
if (IS_ERR(priv->suspend_clk)) {
if (PTR_ERR(priv->suspend_clk) == -EPROBE_DEFER)
return -EPROBE_DEFER;
dev_err(dev, "Suspend Clock not found in Device Tree\n");
priv->suspend_clk = NULL;
}
priv->wake_irq = platform_get_irq_byname_optional(pdev, "wake");
if (priv->wake_irq < 0)
priv->wake_irq = platform_get_irq_byname_optional(pdev, "wakeup");
if (priv->wake_irq >= 0) {
err = devm_request_irq(dev, priv->wake_irq,
brcm_usb_phy_wake_isr, 0,
dev_name(dev), dev);
if (err < 0)
return err;
device_set_wakeup_capable(dev, 1);
} else {
dev_info(dev,
"Wake interrupt missing, system wake not supported\n");
}
return 0;
}
static int brcm_usb_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct brcm_usb_phy_data *priv;
struct phy_provider *phy_provider;
struct device_node *dn = pdev->dev.of_node;
int err;
const char *mode;
const struct match_chip_info *info;
struct regmap *rmap;
int x;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
platform_set_drvdata(pdev, priv);
priv->ini.family_id = brcmstb_get_family_id();
priv->ini.product_id = brcmstb_get_product_id();
info = of_device_get_match_data(&pdev->dev);
if (!info)
return -ENOENT;
info->init_func(&priv->ini);
dev_dbg(dev, "Best mapping table is for %s\n",
priv->ini.family_name);
of_property_read_u32(dn, "brcm,ipp", &priv->ini.ipp);
of_property_read_u32(dn, "brcm,ioc", &priv->ini.ioc);
priv->ini.supported_port_modes = USB_CTLR_MODE_HOST;
err = of_property_read_string(dn, "dr_mode", &mode);
if (err == 0) {
name_to_value(&brcm_dr_mode_to_name[0],
ARRAY_SIZE(brcm_dr_mode_to_name),
mode, &priv->ini.supported_port_modes);
}
/* Default port_mode to supported port_modes */
priv->ini.port_mode = priv->ini.supported_port_modes;
if (of_property_read_bool(dn, "brcm,has-xhci"))
priv->has_xhci = true;
if (of_property_read_bool(dn, "brcm,has-eohci"))
priv->has_eohci = true;
for (x = 0; x < BRCM_REGS_MAX; x++) {
if (info->required_regs[x] >= BRCM_REGS_MAX)
break;
err = brcm_usb_get_regs(pdev, info->required_regs[x],
&priv->ini, false);
if (err)
return -EINVAL;
}
if (info->optional_reg) {
err = brcm_usb_get_regs(pdev, info->optional_reg,
&priv->ini, true);
if (err)
return -EINVAL;
}
err = brcm_usb_phy_dvr_init(pdev, priv, dn);
if (err)
return err;
priv->pm_notifier.notifier_call = brcm_pm_notifier;
register_pm_notifier(&priv->pm_notifier);
mutex_init(&priv->mutex);
/* make sure invert settings are correct */
brcm_usb_init_ipp(&priv->ini);
/*
* Create sysfs entries for mode.
* Remove "dual_select" attribute if not in dual mode
*/
if (priv->ini.supported_port_modes != USB_CTLR_MODE_DRD)
brcm_usb_phy_attrs[1] = NULL;
err = sysfs_create_group(&dev->kobj, &brcm_usb_phy_group);
if (err)
dev_warn(dev, "Error creating sysfs attributes\n");
/* Get piarbctl syscon if it exists */
rmap = syscon_regmap_lookup_by_phandle(dev->of_node,
"syscon-piarbctl");
if (IS_ERR(rmap))
rmap = syscon_regmap_lookup_by_phandle(dev->of_node,
"brcm,syscon-piarbctl");
if (!IS_ERR(rmap))
priv->ini.syscon_piarbctl = rmap;
/* start with everything off */
if (priv->has_xhci)
brcm_usb_uninit_xhci(&priv->ini);
if (priv->has_eohci)
brcm_usb_uninit_eohci(&priv->ini);
brcm_usb_uninit_common(&priv->ini);
clk_disable_unprepare(priv->usb_20_clk);
clk_disable_unprepare(priv->usb_30_clk);
phy_provider = devm_of_phy_provider_register(dev, brcm_usb_phy_xlate);
return PTR_ERR_OR_ZERO(phy_provider);
}
static void brcm_usb_phy_remove(struct platform_device *pdev)
{
struct brcm_usb_phy_data *priv = dev_get_drvdata(&pdev->dev);
sysfs_remove_group(&pdev->dev.kobj, &brcm_usb_phy_group);
unregister_pm_notifier(&priv->pm_notifier);
}
#ifdef CONFIG_PM_SLEEP
static int brcm_usb_phy_suspend(struct device *dev)
{
struct brcm_usb_phy_data *priv = dev_get_drvdata(dev);
if (priv->init_count) {
dev_dbg(dev, "SUSPEND\n");
priv->ini.wake_enabled = device_may_wakeup(dev);
if (priv->phys[BRCM_USB_PHY_3_0].inited)
brcm_usb_uninit_xhci(&priv->ini);
if (priv->phys[BRCM_USB_PHY_2_0].inited)
brcm_usb_uninit_eohci(&priv->ini);
brcm_usb_uninit_common(&priv->ini);
/*
* Handle the clocks unless needed for wake. This has
* to work for both older XHCI->3.0-clks, EOHCI->2.0-clks
* and newer XHCI->2.0-clks/3.0-clks.
*/
if (!priv->ini.wake_enabled) {
if (priv->phys[BRCM_USB_PHY_3_0].inited)
clk_disable_unprepare(priv->usb_30_clk);
if (priv->phys[BRCM_USB_PHY_2_0].inited ||
!priv->has_eohci)
clk_disable_unprepare(priv->usb_20_clk);
}
if (priv->wake_irq >= 0)
enable_irq_wake(priv->wake_irq);
}
return 0;
}
static int brcm_usb_phy_resume(struct device *dev)
{
struct brcm_usb_phy_data *priv = dev_get_drvdata(dev);
if (!priv->ini.wake_enabled) {
clk_prepare_enable(priv->usb_20_clk);
clk_prepare_enable(priv->usb_30_clk);
}
brcm_usb_init_ipp(&priv->ini);
/*
* Initialize anything that was previously initialized.
* Uninitialize anything that wasn't previously initialized.
*/
if (priv->init_count) {
dev_dbg(dev, "RESUME\n");
if (priv->wake_irq >= 0)
disable_irq_wake(priv->wake_irq);
brcm_usb_init_common(&priv->ini);
if (priv->phys[BRCM_USB_PHY_2_0].inited) {
brcm_usb_init_eohci(&priv->ini);
} else if (priv->has_eohci) {
brcm_usb_uninit_eohci(&priv->ini);
clk_disable_unprepare(priv->usb_20_clk);
}
if (priv->phys[BRCM_USB_PHY_3_0].inited) {
brcm_usb_init_xhci(&priv->ini);
} else if (priv->has_xhci) {
brcm_usb_uninit_xhci(&priv->ini);
clk_disable_unprepare(priv->usb_30_clk);
if (!priv->has_eohci)
clk_disable_unprepare(priv->usb_20_clk);
}
} else {
if (priv->has_xhci)
brcm_usb_uninit_xhci(&priv->ini);
if (priv->has_eohci)
brcm_usb_uninit_eohci(&priv->ini);
brcm_usb_uninit_common(&priv->ini);
clk_disable_unprepare(priv->usb_20_clk);
clk_disable_unprepare(priv->usb_30_clk);
}
priv->ini.wake_enabled = false;
return 0;
}
#endif /* CONFIG_PM_SLEEP */
static const struct dev_pm_ops brcm_usb_phy_pm_ops = {
SET_LATE_SYSTEM_SLEEP_PM_OPS(brcm_usb_phy_suspend, brcm_usb_phy_resume)
};
MODULE_DEVICE_TABLE(of, brcm_usb_dt_ids);
static struct platform_driver brcm_usb_driver = {
.probe = brcm_usb_phy_probe,
.remove = brcm_usb_phy_remove,
.driver = {
.name = "brcmstb-usb-phy",
.pm = &brcm_usb_phy_pm_ops,
.of_match_table = brcm_usb_dt_ids,
},
};
module_platform_driver(brcm_usb_driver);
MODULE_AUTHOR("Al Cooper <acooper@broadcom.com>");
MODULE_DESCRIPTION("BRCM USB PHY driver");
MODULE_LICENSE("GPL v2");