Some Qualcomm PMICs integrate an USB Repeater device, used to
convert between eUSB2 and USB 2.0 signaling levels, reachable
in a specific address range over SPMI.
Instead of using the parent SPMI device (the main PMIC) as a kind
of syscon in this driver, register a new SPMI sub-device for EUSB2
and initialize its own regmap with this sub-device's specific base
address, retrieved from the devicetree.
This allows to stop manually adding the register base address to
every R/W call in this driver, as this can be, and is now, handled
by the regmap API instead.
Tested-by: Neil Armstrong <neil.armstrong@linaro.org> # on SM8650-QRD
Reviewed-by: Abel Vesa <abel.vesa@linaro.org>
Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
---
drivers/phy/qualcomm/Kconfig | 2 +
.../phy/qualcomm/phy-qcom-eusb2-repeater.c | 55 ++++++++++++-------
2 files changed, 37 insertions(+), 20 deletions(-)
diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig
index 60a0ead127fa..902a788f35f1 100644
--- a/drivers/phy/qualcomm/Kconfig
+++ b/drivers/phy/qualcomm/Kconfig
@@ -128,7 +128,9 @@ config PHY_QCOM_QUSB2
config PHY_QCOM_EUSB2_REPEATER
tristate "Qualcomm PMIC eUSB2 Repeater Driver"
depends on OF && (ARCH_QCOM || COMPILE_TEST)
+ depends on SPMI
select GENERIC_PHY
+ select REGMAP_SPMI
help
Enable support for the USB high-speed eUSB2 repeater on Qualcomm
PMICs. The repeater is paired with a Synopsys or M31 eUSB2 Phy
diff --git a/drivers/phy/qualcomm/phy-qcom-eusb2-repeater.c b/drivers/phy/qualcomm/phy-qcom-eusb2-repeater.c
index efeec4709a15..88e10020f958 100644
--- a/drivers/phy/qualcomm/phy-qcom-eusb2-repeater.c
+++ b/drivers/phy/qualcomm/phy-qcom-eusb2-repeater.c
@@ -9,6 +9,7 @@
#include <linux/regmap.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
+#include <linux/spmi.h>
/* eUSB2 status registers */
#define EUSB2_RPTR_STATUS 0x08
@@ -66,7 +67,6 @@ struct eusb2_repeater {
struct phy *phy;
struct regulator_bulk_data *vregs;
const struct eusb2_repeater_cfg *cfg;
- u32 base;
enum phy_mode mode;
};
@@ -143,7 +143,6 @@ static int eusb2_repeater_init(struct phy *phy)
struct eusb2_repeater *rptr = phy_get_drvdata(phy);
struct device_node *np = rptr->dev->of_node;
struct regmap *regmap = rptr->regmap;
- u32 base = rptr->base;
u32 poll_val;
s32 dt_val;
int ret;
@@ -154,37 +153,37 @@ static int eusb2_repeater_init(struct phy *phy)
if (ret)
return ret;
- regmap_write(regmap, base + EUSB2_EN_CTL1, EUSB2_RPTR_EN);
+ regmap_write(regmap, EUSB2_EN_CTL1, EUSB2_RPTR_EN);
/* Write registers from init table */
for (int i = 0; i < rptr->cfg->init_tbl_num; i++)
- regmap_write(regmap, base + rptr->cfg->init_tbl[i].reg,
+ regmap_write(regmap, rptr->cfg->init_tbl[i].reg,
rptr->cfg->init_tbl[i].value);
/* Override registers from devicetree values */
if (!of_property_read_u8(np, "qcom,tune-usb2-preem", &val))
- regmap_write(regmap, base + EUSB2_TUNE_USB2_PREEM, val);
+ regmap_write(regmap, EUSB2_TUNE_USB2_PREEM, val);
if (!of_property_read_u8(np, "qcom,tune-usb2-disc-thres", &val))
- regmap_write(regmap, base + EUSB2_TUNE_HSDISC, val);
+ regmap_write(regmap, EUSB2_TUNE_HSDISC, val);
if (!of_property_read_u8(np, "qcom,tune-usb2-amplitude", &val))
- regmap_write(regmap, base + EUSB2_TUNE_IUSB2, val);
+ regmap_write(regmap, EUSB2_TUNE_IUSB2, val);
if (!of_property_read_u8(np, "qcom,tune-res-fsdif", &val))
- regmap_write(regmap, base + EUSB2_TUNE_RES_FSDIF, val);
+ regmap_write(regmap, EUSB2_TUNE_RES_FSDIF, val);
if (!of_property_read_s32(np, "qcom,squelch-detector-bp", &dt_val)) {
for (i = 0; i < ARRAY_SIZE(squelch_detector); i++) {
if (squelch_detector[i] == dt_val) {
- regmap_write(regmap, base + EUSB2_TUNE_SQUELCH_U, i);
+ regmap_write(regmap, EUSB2_TUNE_SQUELCH_U, i);
break;
}
}
}
/* Wait for status OK */
- ret = regmap_read_poll_timeout(regmap, base + EUSB2_RPTR_STATUS, poll_val,
+ ret = regmap_read_poll_timeout(regmap, EUSB2_RPTR_STATUS, poll_val,
poll_val & RPTR_OK, 10, 5);
if (ret)
dev_err(rptr->dev, "initialization timed-out\n");
@@ -197,7 +196,6 @@ static int eusb2_repeater_set_mode(struct phy *phy,
{
struct eusb2_repeater *rptr = phy_get_drvdata(phy);
struct regmap *regmap = rptr->regmap;
- u32 base = rptr->base;
switch (mode) {
case PHY_MODE_USB_HOST:
@@ -206,8 +204,8 @@ static int eusb2_repeater_set_mode(struct phy *phy,
* per eUSB 1.2 Spec. Below implement software workaround until
* PHY and controller is fixing seen observation.
*/
- regmap_write(regmap, base + EUSB2_FORCE_EN_5, F_CLK_19P2M_EN);
- regmap_write(regmap, base + EUSB2_FORCE_VAL_5, V_CLK_19P2M_EN);
+ regmap_write(regmap, EUSB2_FORCE_EN_5, F_CLK_19P2M_EN);
+ regmap_write(regmap, EUSB2_FORCE_VAL_5, V_CLK_19P2M_EN);
break;
case PHY_MODE_USB_DEVICE:
/*
@@ -216,8 +214,8 @@ static int eusb2_repeater_set_mode(struct phy *phy,
* repeater doesn't clear previous value due to shared
* regulators (say host <-> device mode switch).
*/
- regmap_write(regmap, base + EUSB2_FORCE_EN_5, 0);
- regmap_write(regmap, base + EUSB2_FORCE_VAL_5, 0);
+ regmap_write(regmap, EUSB2_FORCE_EN_5, 0);
+ regmap_write(regmap, EUSB2_FORCE_VAL_5, 0);
break;
default:
return -EINVAL;
@@ -242,13 +240,23 @@ static const struct phy_ops eusb2_repeater_ops = {
static int eusb2_repeater_probe(struct platform_device *pdev)
{
+ struct regmap_config eusb2_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 8,
+ .max_register = 0x100,
+ .fast_io = true,
+ };
+ struct spmi_device *sparent;
struct eusb2_repeater *rptr;
+ struct spmi_subdevice *sub_sdev;
struct device *dev = &pdev->dev;
struct phy_provider *phy_provider;
struct device_node *np = dev->of_node;
- u32 res;
int ret;
+ if (!dev->parent)
+ return -ENODEV;
+
rptr = devm_kzalloc(dev, sizeof(*rptr), GFP_KERNEL);
if (!rptr)
return -ENOMEM;
@@ -260,15 +268,21 @@ static int eusb2_repeater_probe(struct platform_device *pdev)
if (!rptr->cfg)
return -EINVAL;
- rptr->regmap = dev_get_regmap(dev->parent, NULL);
- if (!rptr->regmap)
+ sparent = to_spmi_device(dev->parent);
+ if (!sparent)
return -ENODEV;
- ret = of_property_read_u32(np, "reg", &res);
+ sub_sdev = devm_spmi_subdevice_alloc_and_add(dev, sparent);
+ if (IS_ERR(sub_sdev))
+ return PTR_ERR(sub_sdev);
+
+ ret = of_property_read_u32(np, "reg", &eusb2_regmap_config.reg_base);
if (ret < 0)
return ret;
- rptr->base = res;
+ rptr->regmap = devm_regmap_init_spmi_ext(&sub_sdev->sdev, &eusb2_regmap_config);
+ if (IS_ERR(rptr->regmap))
+ return -ENODEV;
ret = eusb2_repeater_init_vregs(rptr);
if (ret < 0) {
@@ -335,3 +349,4 @@ module_platform_driver(eusb2_repeater_driver);
MODULE_DESCRIPTION("Qualcomm PMIC eUSB2 Repeater driver");
MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("SPMI");
--
2.52.0
On Wed, Jan 14, 2026 at 09:39:54AM +0100, AngeloGioacchino Del Regno wrote:
> Some Qualcomm PMICs integrate an USB Repeater device, used to
> convert between eUSB2 and USB 2.0 signaling levels, reachable
> in a specific address range over SPMI.
>
> Instead of using the parent SPMI device (the main PMIC) as a kind
> of syscon in this driver, register a new SPMI sub-device for EUSB2
> and initialize its own regmap with this sub-device's specific base
> address, retrieved from the devicetree.
>
> This allows to stop manually adding the register base address to
> every R/W call in this driver, as this can be, and is now, handled
> by the regmap API instead.
Same comments and actually one more.
...
> + struct regmap_config eusb2_regmap_config = {
> + .reg_bits = 16,
> + .val_bits = 8,
> + .max_register = 0x100,
> + .fast_io = true,
> + };
This is third time of the same. Make it part of SPMI core and export to
the users. Or are they semantically different like different slices?
In that case you can export it under generic name like
spmi_default_slice_regmap_config
--
With Best Regards,
Andy Shevchenko
Il 14/01/26 09:59, Andy Shevchenko ha scritto:
> On Wed, Jan 14, 2026 at 09:39:54AM +0100, AngeloGioacchino Del Regno wrote:
>> Some Qualcomm PMICs integrate an USB Repeater device, used to
>> convert between eUSB2 and USB 2.0 signaling levels, reachable
>> in a specific address range over SPMI.
>>
>> Instead of using the parent SPMI device (the main PMIC) as a kind
>> of syscon in this driver, register a new SPMI sub-device for EUSB2
>> and initialize its own regmap with this sub-device's specific base
>> address, retrieved from the devicetree.
>>
>> This allows to stop manually adding the register base address to
>> every R/W call in this driver, as this can be, and is now, handled
>> by the regmap API instead.
>
> Same comments and actually one more.
>
> ...
>
>> + struct regmap_config eusb2_regmap_config = {
>> + .reg_bits = 16,
>> + .val_bits = 8,
>> + .max_register = 0x100,
>> + .fast_io = true,
>> + };
>
> This is third time of the same. Make it part of SPMI core and export to
> the users. Or are they semantically different like different slices?
> In that case you can export it under generic name like
>
> spmi_default_slice_regmap_config
>
There are more complicated devices around that I didn't port to the new
spmi subdevices, and I really don't want to make a default for now.
At least some of those need different params (including some MediaTek ones
that are not upstream yet).
Can we please let this in and *then* see how much can be commonized after
the majority of more complicated drivers are migrated in the future?
Regards,
Angelo
On Wed, Jan 14, 2026 at 10:26:42AM +0100, AngeloGioacchino Del Regno wrote:
> Il 14/01/26 09:59, Andy Shevchenko ha scritto:
> > On Wed, Jan 14, 2026 at 09:39:54AM +0100, AngeloGioacchino Del Regno wrote:
...
> > > + struct regmap_config eusb2_regmap_config = {
> > > + .reg_bits = 16,
> > > + .val_bits = 8,
> > > + .max_register = 0x100,
> > > + .fast_io = true,
> > > + };
> >
> > This is third time of the same. Make it part of SPMI core and export to
> > the users. Or are they semantically different like different slices?
> > In that case you can export it under generic name like
> >
> > spmi_default_slice_regmap_config
>
> There are more complicated devices around that I didn't port to the new
> spmi subdevices, and I really don't want to make a default for now.
>
> At least some of those need different params (including some MediaTek ones
> that are not upstream yet).
>
> Can we please let this in and *then* see how much can be commonized after
> the majority of more complicated drivers are migrated in the future?
Sure, but it looks like a pattern currently...
--
With Best Regards,
Andy Shevchenko
© 2016 - 2026 Red Hat, Inc.