diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-23 13:28:57 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-23 13:28:57 -0700 |
| commit | 4352b8aee98005853aa63f57d6377282de17a33f (patch) | |
| tree | 27614d7a9ef05c32d089004e104364ce42324837 /drivers/phy/broadcom/phy-bcm-sr-usb.c | |
| download | linux-stable-4352b8aee98005853aa63f57d6377282de17a33f.tar.gz linux-stable-4352b8aee98005853aa63f57d6377282de17a33f.zip | |
Merge tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linuxgrafted
Pull I3C updates from Alexandre Belloni:
"A new driver this cycle, for the AMD AXI I3C controller.
There are also new features such as IBI wakeup support, SETAASA device
discovery and ACPI support for the the DAA methods, meaning we can now
communicate with the SPD devices on DDR5 modules.
New driver:
- AMD AXI I3C master controller
Subsystem:
- I3C Common Command Code (CCC) handling improvements, especially
around GET CCCs
- SETAASA device discovery support
- ACPI support for all existing DAA methods like SETDASA, SETNEWDA as
well as I2C devices on I3C bus
- IBI-based wakeup support
Drivers:
- dw: SETAASA support
- mipi-i3c-hci: advertise IBI wakeup capability, AMD_PT I3C
controller support, PIO queue management support for HCI v1.2
- renesas: improve suspend to RAM support, add runtime PM support"
* tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: (70 commits)
i3c: dw: reduce do_daa time if there's no client
i3c: mipi-i3c-hci: Add support for AMD_PT I3C controller
i3c: mipi-i3c-hci: Add PIO queue management support for HCI v1.2
i3c: mipi-i3c-hci: Fix missing STAT_IBI_STATUS_THLD in PIO mode
i3c: dw: rename "pclk" to "apb" to match dt-binding
i3c: dw: make struct dw_i3c_cmd smaller
i3c: dw: use COMMAND_PORT_TRANSFER_ARG instead of hardcoding
i3c: renesas: Don't register devices when ENTDAA times out
i3c: master: dw-i3c-master: fix OD timing for first broadcast
i3c: mipi-i3c-hci: Advertise IBI wakeup capability
i3c: mipi-i3c-hci: Factor out i3c_hci_sysdev()
i3c: mipi-i3c-hci-pci: Propagate I3C wakeup requirements to PCI
i3c: master: Reject IBI requests from non-IBI-capable devices
i3c: master: Add helper to query bus wakeup requirements
i3c: master: Report wakeup events for IBIs
i3c: master: Support IBI-based wakeup capability
i3c: master: Fix potential UAF in i3c_device_match()
i3c: master: Fix potential UAF in i3c_device_uevent()
i3c: Make dev->desc locking assumptions explicit
i3c: master: Fix use-after-free of master->this
...
Diffstat (limited to 'drivers/phy/broadcom/phy-bcm-sr-usb.c')
| -rw-r--r-- | drivers/phy/broadcom/phy-bcm-sr-usb.c | 338 |
1 files changed, 338 insertions, 0 deletions
diff --git a/drivers/phy/broadcom/phy-bcm-sr-usb.c b/drivers/phy/broadcom/phy-bcm-sr-usb.c new file mode 100644 index 000000000..6bcfe8360 --- /dev/null +++ b/drivers/phy/broadcom/phy-bcm-sr-usb.c @@ -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"); |
