linux/drivers/phy/broadcom/phy-brcm-usb.c
Rafał Miłecki 32942d33d6 phy: phy-brcm-usb: fixup BCM4908 support
Just like every other family BCM4908 should get its own enum value. That
is required to properly handle it in chipset conditional code.

The real change is excluding BCM4908 from the PLL reprogramming code
(see brcmusb_usb3_pll_54mhz()). I'm not sure what's the BCM4908
reference clock frequency but:
1. BCM4908 custom driver from Broadcom's SDK doesn't reprogram PLL
2. Doing that in Linux driver stopped PHY handling some USB 3.0 devices

This change makes USB 3.0 PHY recognize e.g.:
1. 04e8:6860 - Samsung Electronics Co., Ltd Galaxy series, misc. (MTP mode)
2. 1058:259f - Western Digital My Passport 259F

Broadcom's STB SoCs come with a set of SUN_TOP_CTRL_* registers that
allow reading chip family and product ids. Such a block & register is
missing on BCM4908 so this commit introduces "compatible" string
specific binding.

Fixes: 4b402fa8e0 ("phy: phy-brcm-usb: support PHY on the BCM4908")
Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Link: https://lore.kernel.org/r/20220218172459.10431-1-zajec5@gmail.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
2022-02-25 14:32:37 +05:30

681 lines
17 KiB
C

// 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/of_device.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 phy *gphy = dev_id;
pm_wakeup_event(&gphy->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,
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.mode));
}
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) {
brcm_usb_set_dual_select(&priv->ini, value);
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_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,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(pdev, "wake");
if (priv->wake_irq < 0)
priv->wake_irq = platform_get_irq_byname(pdev, "wakeup");
if (priv->wake_irq >= 0) {
err = devm_request_irq(dev, priv->wake_irq,
brcm_usb_phy_wake_isr, 0,
dev_name(dev), gphy);
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.mode = 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.mode);
}
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.mode != 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 int 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);
return 0;
}
#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.suspend_with_clocks) {
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);
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_ALIAS("platform:brcmstb-usb-phy");
MODULE_AUTHOR("Al Cooper <acooper@broadcom.com>");
MODULE_DESCRIPTION("BRCM USB PHY driver");
MODULE_LICENSE("GPL v2");