diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-19 10:09:22 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-19 10:09:22 -0700 |
| commit | f4cdf7ca9a1fdcca413157df19753f388a5a224e (patch) | |
| tree | 60a31895b2446cd7fa27514e8c2843534a0de0da /drivers/phy/broadcom/phy-bcm-sr-usb.c | |
| download | linux-stable-f4cdf7ca9a1fdcca413157df19753f388a5a224e.tar.gz linux-stable-f4cdf7ca9a1fdcca413157df19753f388a5a224e.zip | |
Merge tag 'media/v7.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-mediagrafted
Pull media updates from Mauro Carvalho Chehab:
- v4l2-core: added ISP statistics support and per-block validation
- v4l2-core: Allow unknown HDR10 white point and luminance
- New camera sensors: Sony IMX678 and IMX471m, Himax HM1092 IR sensor
- New codec: Milos: VPU v2.0 codec support
- isp driver: gained support for Dreamchip RPPX1 ISP framework
- vsp1 driver: gained support for RZ/T2H and RZ/N2H
- Novalake driver: gained CVS support for new NVL hardware
- dvb-core: fix feed leak on failed DMX_ADD_PID
- several driver fixes, cleanups and minor improvements
* tag 'media/v7.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media: (308 commits)
media: ipu-bridge: check all DMI entries when overriding sensor rotation
media: v4l2-async: avoid deleting unlinked ASC entry on link error
media: rzg2l-cru: Align bytesperline to hardware DMA stride requirement
media: intel/ipu6: fix async notifier cleanup leak on parse error
media: staging/ipu7: fix async notifier UAF on probe error path
media: amd: isp4: fix self-deadlock in isp4sd_pwron_and_init() error path
media: amd: isp4: release partial allocations in isp4if_alloc_fw_gpumem()
media: rcar-isp: Fix VSPX reference leaks
media: rcar-isp: Release ISPCORE resources
media: i2c: imx415: Release runtime PM reference on VBLANK error
media: i2c: imx415: Return test pattern write errors
media: renesas: vsp1: Declare index variables in for loop statement
media: renesas: vsp1: Make reset control optional to support platforms without a reset line
media: dt-bindings: media: renesas,vsp1: Document RZ/T2H and RZ/N2H SoCs
media: dt-bindings: media: renesas,fcp: Document RZ/T2H and RZ/N2H SoCs
media: nxp: imx8-isi: Add additional 32-bit RGB format support
media: nxp: imx8-isi: Add 16-bit raw Bayer format support
media: nxp: imx8-isi: Implement per-stream reference counting for multiplexed streams
media: nxp: imx8-isi: Use BIT_ULL() for 64-bit stream masks
media: nxp: imx8-isi: Correct color map between V4L2 and ISI
...
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"); |
