diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 578a439e71b5..d25b2ece65d3 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -174,4 +174,6 @@ source "drivers/usb/typec/Kconfig" source "drivers/usb/roles/Kconfig" +source "drivers/usb/repeater/Kconfig" + endif # USB_SUPPORT diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 643edf5fe18c..dabeed7812fd 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -66,3 +66,5 @@ obj-$(CONFIG_USBIP_CORE) += usbip/ obj-$(CONFIG_TYPEC) += typec/ obj-$(CONFIG_USB_ROLE_SWITCH) += roles/ + +obj-$(CONFIG_USB_REPEATER) += repeater/ diff --git a/drivers/usb/repeater/Kconfig b/drivers/usb/repeater/Kconfig new file mode 100644 index 000000000000..c1b8ce62c586 --- /dev/null +++ b/drivers/usb/repeater/Kconfig @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# USB eUSB2 repeater driver configuration +# +menu "USB eUSB2 repeater drivers" + +config USB_REPEATER + tristate "usb repeater framework" + help + Enable this to allow eUSB2 PHY driver and connected repeater driver + to bind with together, and provide eUS2 PHY driver ways to perform + set of operations with repeater for USB2 functionality using eUSB2 + PHY and eUSB2 repeater. + +config QTI_PMIC_EUSB2_REPEATER + tristate "QTI PMIC eUSB2 repeater driver" + depends on USB_REPEATER && SPMI + select REGMAP_SPMI + help + Enable this to support the USB EUSB2 REPEATER on PMIC chips. This driver + provides support to reset, initialiated, power up and configure eUSB2 + repeater for USB HS/FS/LS functionality where eUSB2 repeater is used. + + To compile this driver as a module, choose M here. +endmenu diff --git a/drivers/usb/repeater/Makefile b/drivers/usb/repeater/Makefile new file mode 100644 index 000000000000..7933fb5fd14d --- /dev/null +++ b/drivers/usb/repeater/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for USB eUSB2 drivers +# +obj-$(CONFIG_USB_REPEATER) += repeater.o +obj-$(CONFIG_QTI_PMIC_EUSB2_REPEATER) += repeater-qti-pmic-eusb2.o diff --git a/drivers/usb/repeater/repeater-qti-pmic-eusb2.c b/drivers/usb/repeater/repeater-qti-pmic-eusb2.c new file mode 100644 index 000000000000..d74b007ea595 --- /dev/null +++ b/drivers/usb/repeater/repeater-qti-pmic-eusb2.c @@ -0,0 +1,547 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define EUSB2_3P0_VOL_MIN 3075000 /* uV */ +#define EUSB2_3P0_VOL_MAX 3300000 /* uV */ +#define EUSB2_3P0_HPM_LOAD 3500 /* uA */ + +#define EUSB2_1P8_VOL_MIN 1800000 /* uV */ +#define EUSB2_1P8_VOL_MAX 1800000 /* uV */ +#define EUSB2_1P8_HPM_LOAD 32000 /* uA */ + +/* eUSB2 status registers */ +#define EUSB2_RPTR_STATUS 0x08 +#define RPTR_OK BIT(7) + +#define EUSB2_RPTR_INFRA_STATUS 0x09 +#define EUSB2_RPTR_DROPOUT_REASON 0x0A + +/* eUSB2 control registers */ +#define EUSB2_MODE_CTL 0x40 +#define HOST_SEL BIT(1) +#define PERIPH_SEL BIT(0) + +#define EUSB2_USB_HIZ_CTL 0x41 +#define HIZ_EN BIT(7) + +#define EUSB2_AUTO_RESUME_EN 0x42 +#define AUTO_RESUME_EN BIT(7) + +#define EUSB2_EN_CTL1 0x46 +#define EUSB2_RPTR_EN BIT(7) + +/* eUSB2 tuning parameters registers */ +#define EUSB2_TUNE_USB2_CROSSOVER 0x50 +#define TUNE_USB2_CROSSOVER_MASK 0x07 + +#define EUSB2_TUNE_IUSB2 0x51 +#define TUNE_IUSB2_MASK 0x0F + +#define EUSB2_TUNE_RES_FSDIF 0x52 +#define TUNE_RES_FSIDF_MASK 0x07 + +#define EUSB2_TUNE_HSDISC 0x53 +#define TUNE_HSDISC_MASK 0x07 + +#define EUSB2_TUNE_SQUELCH_U 0x54 +#define TUNE_SQELCH_U_MASK 0x07 + +#define EUSB2_TUNE_USB2_SLEW 0x55 +#define TUNE_USB2_SLEW_MASK 0x03 + +#define EUSB2_TUNE_USB2_EQU 0x56 +#define TUNE_USB2_EQU_MASK 0x03 + +#define EUSB2_TUNE_USB2_PREEM 0x57 +#define TUNE_USB2_PREEM_MASK 0x07 + +#define EUSB2_TUNE_USB2_HS_COMP_CUR 0x58 +#define TUNE_USB2_HS_COMP_CURRENT_MASK 0x03 + +#define EUSB2_TUNE_EUSB_SLEW 0x59 +#define TUNE_EUSB_SLEW_MASK 0x03 + +#define EUSB2_TUNE_EUSB_EQU 0x5A +#define TUNE_EUSB_EQU_MASK 0x03 + +#define EUSB2_TUNE_EUSB_HS_COMP_CUR 0x5B +#define TUNE_EUSB_HS_COMP_CUR_MASK 0x03 + +/* Force Enable registers */ +#define EUSB2_FORCE_EN_5 0xE8 +#define F_CLK_19P2M_EN BIT(6) +#define F_CLK_19P2M_EN_SHIFT 6 + +#define EUSB2_FORCE_VAL_5 0xED +#define V_CLK_19P2M_EN BIT(6) +#define V_CLK_19P2M_EN_SHIFT 6 + +struct eusb2_repeater { + struct usb_repeater ur; + struct regmap *regmap; + u16 reg_base; + struct regulator *vdd18; + struct regulator *vdd3; + bool power_enabled; + + struct dentry *root; + u8 usb2_crossover; + u8 iusb2; + u8 res_fsdif; + u8 hsdisc; + u8 squelch_u; + u8 usb2_slew; + u8 usb2_equ; + u8 usb2_preem; + u8 hs_comp_current; + u8 eusb_slew; + u8 eusb_equ; + u8 eusb_hs_comp_current; + + u32 *param_override_seq; + u8 param_override_seq_cnt; +}; + +/* Perform one or more register read */ +static int eusb2_repeater_reg_read(struct eusb2_repeater *er, + u8 *read_val, u16 reg_offset, size_t reg_count) +{ + int ret; + + ret = regmap_bulk_read(er->regmap, er->reg_base + reg_offset, + read_val, reg_count); + if (ret) + dev_err(er->ur.dev, "read failed: addr=0x%04x, ret=%d\n", + er->reg_base + reg_offset, ret); + return ret; +} + +/* Perform multiple registers write using given block of data */ +static int eusb2_repeater_bulk_reg_write(struct eusb2_repeater *er, + u16 reg_offset, const void *write_val, size_t reg_count) +{ + int ret; + + ret = regmap_bulk_write(er->regmap, er->reg_base + reg_offset, + write_val, reg_count); + if (ret) + dev_err(er->ur.dev, "bulk write failed: addr=0x%04x, ret=%d\n", + er->reg_base + reg_offset, ret); + return ret; +} + +/* Perform specific register write using given byte value */ +static inline int eusb2_repeater_reg_write(struct eusb2_repeater *er, + u16 reg_offset, u8 write_val) +{ + return eusb2_repeater_bulk_reg_write(er, reg_offset, &write_val, 1); +} + +/* Update specified register using given bit mask and value */ +static int eusb2_repeater_masked_write(struct eusb2_repeater *er, + u16 reg_offset, u8 mask, u8 write_val) +{ + int ret; + + ret = regmap_update_bits(er->regmap, er->reg_base + reg_offset, + mask, write_val); + if (ret) + dev_err(er->ur.dev, "write failed: addr=0x%04x, ret=%d\n", + er->reg_base + reg_offset, ret); + return ret; +} + +static void eusb2_repeater_update_seq(struct eusb2_repeater *er, + u32 *seq, u8 cnt) +{ + int i; + + dev_dbg(er->ur.dev, "param override seq count:%d\n", cnt); + for (i = 0; i < cnt; i = i+2) { + dev_dbg(er->ur.dev, "write 0x%02x to 0x%02x\n", + seq[i], seq[i+1]); + eusb2_repeater_reg_write(er, seq[i+1], seq[i]); + } +} + +static void eusb2_repeater_create_debugfs(struct eusb2_repeater *er) +{ + er->root = debugfs_create_dir(dev_name(er->ur.dev), NULL); + debugfs_create_x8("usb2_crossover", 0644, er->root, + &er->usb2_crossover); + debugfs_create_x8("iusb2", 0644, er->root, &er->iusb2); + debugfs_create_x8("res_fsdif", 0644, er->root, &er->res_fsdif); + debugfs_create_x8("hsdisc", 0644, er->root, &er->hsdisc); + debugfs_create_x8("squelch_u", 0644, er->root, &er->squelch_u); + debugfs_create_x8("usb2_slew", 0644, er->root, &er->usb2_slew); + debugfs_create_x8("usb2_equ", 0644, er->root, &er->usb2_equ); + debugfs_create_x8("usb2_preem", 0644, er->root, &er->usb2_preem); + debugfs_create_x8("hs_comp_current", 0644, er->root, + &er->hs_comp_current); + debugfs_create_x8("eusb_slew", 0644, er->root, &er->eusb_slew); + debugfs_create_x8("eusb_equ", 0644, er->root, &er->eusb_equ); + debugfs_create_x8("eusb_hs_comp_current", 0644, er->root, + &er->eusb_hs_comp_current); +} + +static int eusb2_repeater_power(struct eusb2_repeater *er, bool on) +{ + int ret = 0; + + dev_dbg(er->ur.dev, "%s turn %s regulators. power_enabled:%d\n", + __func__, on ? "on" : "off", er->power_enabled); + + if (er->power_enabled == on) { + dev_dbg(er->ur.dev, "regulators' regulators are already ON.\n"); + return 0; + } + + if (!on) + goto disable_vdd3; + + ret = regulator_set_load(er->vdd18, EUSB2_1P8_HPM_LOAD); + if (ret < 0) { + dev_err(er->ur.dev, "Unable to set HPM of vdd18:%d\n", ret); + goto err_vdd18; + } + + ret = regulator_set_voltage(er->vdd18, EUSB2_1P8_VOL_MIN, + EUSB2_1P8_VOL_MAX); + if (ret) { + dev_err(er->ur.dev, + "Unable to set voltage for vdd18:%d\n", ret); + goto put_vdd18_lpm; + } + + ret = regulator_enable(er->vdd18); + if (ret) { + dev_err(er->ur.dev, "Unable to enable vdd18:%d\n", ret); + goto unset_vdd18; + } + + ret = regulator_set_load(er->vdd3, EUSB2_3P0_HPM_LOAD); + if (ret < 0) { + dev_err(er->ur.dev, "Unable to set HPM of vdd3:%d\n", ret); + goto disable_vdd18; + } + + ret = regulator_set_voltage(er->vdd3, EUSB2_3P0_VOL_MIN, + EUSB2_3P0_VOL_MAX); + if (ret) { + dev_err(er->ur.dev, + "Unable to set voltage for vdd3:%d\n", ret); + goto put_vdd3_lpm; + } + + ret = regulator_enable(er->vdd3); + if (ret) { + dev_err(er->ur.dev, "Unable to enable vdd3:%d\n", ret); + goto unset_vdd3; + } + + er->power_enabled = true; + dev_dbg(er->ur.dev, "eUSB2 repeater egulators are turned ON.\n"); + return ret; + +disable_vdd3: + ret = regulator_disable(er->vdd3); + if (ret) + dev_err(er->ur.dev, "Unable to disable vdd3:%d\n", ret); + +unset_vdd3: + ret = regulator_set_voltage(er->vdd3, 0, EUSB2_3P0_VOL_MAX); + if (ret) + dev_err(er->ur.dev, + "Unable to set (0) voltage for vdd3:%d\n", ret); + +put_vdd3_lpm: + ret = regulator_set_load(er->vdd3, 0); + if (ret < 0) + dev_err(er->ur.dev, "Unable to set (0) HPM of vdd3\n"); + +disable_vdd18: + ret = regulator_disable(er->vdd18); + if (ret) + dev_err(er->ur.dev, "Unable to disable vdd18:%d\n", ret); + +unset_vdd18: + ret = regulator_set_voltage(er->vdd18, 0, EUSB2_1P8_VOL_MAX); + if (ret) + dev_err(er->ur.dev, + "Unable to set (0) voltage for vdd18:%d\n", ret); + +put_vdd18_lpm: + ret = regulator_set_load(er->vdd18, 0); + if (ret < 0) + dev_err(er->ur.dev, "Unable to set LPM of vdd18\n"); + +err_vdd18: + /* case handling when regulator turning on failed */ + if (!er->power_enabled) + return -EINVAL; + + er->power_enabled = false; + dev_dbg(er->ur.dev, "eUSB2 repeater's regulators are turned OFF.\n"); + return ret; +} + +#define INIT_MAX_CNT 5 +static int eusb2_repeater_init(struct usb_repeater *ur) +{ + u8 status; + struct eusb2_repeater *er = + container_of(ur, struct eusb2_repeater, ur); + unsigned int rptr_init_cnt = INIT_MAX_CNT; + + /* override init sequence using devicetree based values */ + eusb2_repeater_update_seq(er, er->param_override_seq, + er->param_override_seq_cnt); + + /* override tune params using debugfs based values */ + if (er->usb2_crossover && er->usb2_crossover <= 0x7) + eusb2_repeater_masked_write(er, EUSB2_TUNE_USB2_CROSSOVER, + TUNE_USB2_CROSSOVER_MASK, er->usb2_crossover); + + if (er->iusb2 && er->iusb2 <= 0xf) + eusb2_repeater_masked_write(er, EUSB2_TUNE_IUSB2, + TUNE_IUSB2_MASK, er->iusb2); + + if (er->res_fsdif && er->res_fsdif <= 0x7) + eusb2_repeater_masked_write(er, EUSB2_TUNE_RES_FSDIF, + TUNE_RES_FSIDF_MASK, er->res_fsdif); + + if (er->hsdisc && er->hsdisc <= 0x7) + eusb2_repeater_masked_write(er, EUSB2_TUNE_HSDISC, + TUNE_HSDISC_MASK, er->hsdisc); + + if (er->squelch_u && er->squelch_u <= 0x7) + eusb2_repeater_masked_write(er, EUSB2_TUNE_SQUELCH_U, + TUNE_SQELCH_U_MASK, er->squelch_u); + + if (er->usb2_slew && er->usb2_slew <= 0x3) + eusb2_repeater_masked_write(er, EUSB2_TUNE_USB2_SLEW, + TUNE_USB2_SLEW_MASK, er->usb2_slew); + + if (er->usb2_equ && er->usb2_equ <= 0x3) + eusb2_repeater_masked_write(er, EUSB2_TUNE_USB2_EQU, + TUNE_USB2_EQU_MASK, er->usb2_equ); + + if (er->usb2_preem && er->usb2_preem <= 0x7) + eusb2_repeater_masked_write(er, EUSB2_TUNE_USB2_PREEM, + TUNE_USB2_PREEM_MASK, er->usb2_preem); + + if (er->hs_comp_current && er->hs_comp_current <= 0x3) + eusb2_repeater_masked_write(er, EUSB2_TUNE_USB2_HS_COMP_CUR, + TUNE_USB2_HS_COMP_CURRENT_MASK, er->hs_comp_current); + + if (er->eusb_slew && er->eusb_slew <= 0x3) + eusb2_repeater_masked_write(er, EUSB2_TUNE_EUSB_SLEW, + TUNE_EUSB_SLEW_MASK, er->eusb_slew); + + if (er->eusb_equ && er->eusb_equ <= 0x3) + eusb2_repeater_masked_write(er, EUSB2_TUNE_EUSB_EQU, + TUNE_EUSB_EQU_MASK, er->eusb_equ); + + if (er->eusb_hs_comp_current && er->eusb_hs_comp_current <= 0x3) + eusb2_repeater_masked_write(er, EUSB2_TUNE_EUSB_HS_COMP_CUR, + TUNE_EUSB_HS_COMP_CUR_MASK, er->eusb_hs_comp_current); + /* + * CM.Lx is prohibited when repeater is already into Lx state as + * per eUSB 1.2 Spec. Below implement software workaround until + * PHY and controller is fixing seen observation. + */ + if (ur->flags & PHY_HOST_MODE) { + eusb2_repeater_masked_write(er, EUSB2_FORCE_EN_5, + F_CLK_19P2M_EN, (1 << F_CLK_19P2M_EN_SHIFT)); + eusb2_repeater_masked_write(er, EUSB2_FORCE_VAL_5, + V_CLK_19P2M_EN, (1 << V_CLK_19P2M_EN_SHIFT)); + } else { + /* + * In device mode clear host mode related workaround as there + * is no repeater reset available, and enable/disable of + * repeater doesn't clear previous value due to shared + * regulators (say host <-> device mode switch). + */ + eusb2_repeater_masked_write(er, EUSB2_FORCE_EN_5, + F_CLK_19P2M_EN, (0 << F_CLK_19P2M_EN_SHIFT)); + eusb2_repeater_masked_write(er, EUSB2_FORCE_VAL_5, + V_CLK_19P2M_EN, (0 << V_CLK_19P2M_EN_SHIFT)); + } + + /* Wait for RPTR_STATUS for OK */ + do { + eusb2_repeater_reg_read(er, &status, EUSB2_RPTR_STATUS, 1); + if (status & RPTR_OK) + break; + usleep_range(10, 20); + } while (--rptr_init_cnt); + + dev_info(er->ur.dev, "eUSB2 repeater status:%s\n", + (status & RPTR_OK) ? "OK" : "NOT OK"); + return 0; +} + +static int eusb2_repeater_reset(struct usb_repeater *ur, + bool bring_out_of_reset) +{ + struct eusb2_repeater *er = + container_of(ur, struct eusb2_repeater, ur); + + dev_dbg(ur->dev, "eUSB2 repeater %s\n", + bring_out_of_reset ? "out of reset" : "hold into reset"); + eusb2_repeater_masked_write(er, EUSB2_EN_CTL1, EUSB2_RPTR_EN, + bring_out_of_reset ? EUSB2_RPTR_EN : 0x0); + return 0; +} + +static int eusb2_repeater_powerup(struct usb_repeater *ur) +{ + struct eusb2_repeater *er = + container_of(ur, struct eusb2_repeater, ur); + + return eusb2_repeater_power(er, true); +} + +static int eusb2_repeater_powerdown(struct usb_repeater *ur) +{ + struct eusb2_repeater *er = + container_of(ur, struct eusb2_repeater, ur); + + return eusb2_repeater_power(er, false); +} + +static int eusb2_repeater_probe(struct platform_device *pdev) +{ + struct eusb2_repeater *er; + struct device *dev = &pdev->dev; + int ret = 0, num_elem, base; + + er = devm_kzalloc(dev, sizeof(*er), GFP_KERNEL); + if (!er) { + ret = -ENOMEM; + goto err_probe; + } + + er->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!er->regmap) { + dev_err(&pdev->dev, "failed to get parent's regmap\n"); + ret = -EINVAL; + goto err_probe; + } + + ret = of_property_read_u32(pdev->dev.of_node, "reg", &base); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get reg base address:%d\n", ret); + goto err_probe; + } + + er->reg_base = base; + er->vdd3 = devm_regulator_get(dev, "vdd3"); + if (IS_ERR(er->vdd3)) { + dev_err(dev, "unable to get vdd3 supply\n"); + ret = PTR_ERR(er->vdd3); + goto err_probe; + } + + er->vdd18 = devm_regulator_get(dev, "vdd18"); + if (IS_ERR(er->vdd18)) { + dev_err(dev, "unable to get vdd18 supply\n"); + ret = PTR_ERR(er->vdd18); + goto err_probe; + } + + num_elem = of_property_count_elems_of_size(dev->of_node, + "qcom,param-override-seq", + sizeof(*er->param_override_seq)); + if (num_elem > 0) { + if (num_elem % 2) { + dev_err(dev, "invalid param_override_seq_len\n"); + ret = -EINVAL; + goto err_probe; + } + + er->param_override_seq_cnt = num_elem; + er->param_override_seq = devm_kcalloc(dev, + er->param_override_seq_cnt, + sizeof(*er->param_override_seq), GFP_KERNEL); + if (!er->param_override_seq) { + ret = -ENOMEM; + goto err_probe; + } + + ret = of_property_read_u32_array(dev->of_node, + "qcom,param-override-seq", + er->param_override_seq, + er->param_override_seq_cnt); + if (ret) { + dev_err(dev, "qcom,param-override-seq read failed %d\n", + ret); + goto err_probe; + } + } + + er->ur.dev = dev; + platform_set_drvdata(pdev, er); + + er->ur.init = eusb2_repeater_init; + er->ur.reset = eusb2_repeater_reset; + er->ur.powerup = eusb2_repeater_powerup; + er->ur.powerdown = eusb2_repeater_powerdown; + + ret = usb_add_repeater_dev(&er->ur); + if (ret) + goto err_probe; + + eusb2_repeater_create_debugfs(er); + return 0; + +err_probe: + return ret; +} + +static int eusb2_repeater_remove(struct platform_device *pdev) +{ + struct eusb2_repeater *er = platform_get_drvdata(pdev); + + if (!er) + return 0; + + debugfs_remove_recursive(er->root); + usb_remove_repeater_dev(&er->ur); + eusb2_repeater_power(er, false); + return 0; +} + +static const struct of_device_id eusb2_repeater_id_table[] = { + { + .compatible = "qcom,pmic-eusb2-repeater", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, eusb2_repeater_id_table); + +static struct platform_driver eusb2_repeater_driver = { + .probe = eusb2_repeater_probe, + .remove = eusb2_repeater_remove, + .driver = { + .name = "eusb2-repeater", + .of_match_table = of_match_ptr(eusb2_repeater_id_table), + }, +}; + +module_platform_driver(eusb2_repeater_driver); +MODULE_DESCRIPTION("QTI PMIC eUSB2 repeater driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/repeater/repeater.c b/drivers/usb/repeater/repeater.c new file mode 100644 index 000000000000..424d3d83f9cf --- /dev/null +++ b/drivers/usb/repeater/repeater.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ +#include +#include +#include +#include +#include +#include +#include + +static LIST_HEAD(repeater_list); +static DEFINE_SPINLOCK(repeater_lock); + +void usb_put_repeater(struct usb_repeater *r) +{ + if (r) { + put_device(r->dev); + if (r->dev->driver && r->dev->driver->owner) + module_put(r->dev->driver->owner); + } +} +EXPORT_SYMBOL(usb_put_repeater); + +static struct usb_repeater *of_usb_find_repeater(struct device_node *node) +{ + struct usb_repeater *r; + + if (!of_device_is_available(node)) + return ERR_PTR(-ENODEV); + + list_for_each_entry(r, &repeater_list, head) { + if (node != r->dev->of_node) + continue; + return r; + } + + return ERR_PTR(-EPROBE_DEFER); +} + +static void devm_usb_repeater_release_by_node(struct device *dev, void *res) +{ + usb_put_repeater(res); +} + +struct usb_repeater *devm_usb_get_repeater_by_node(struct device *dev, + struct device_node *node) +{ + struct usb_repeater *r = ERR_PTR(-ENOMEM); + struct usb_repeater *r_devm; + unsigned long flags; + + r_devm = devres_alloc(devm_usb_repeater_release_by_node, + sizeof(*r_devm), GFP_KERNEL); + if (!r_devm) { + dev_dbg(dev, "failed to allocate memory for r_devm\n"); + return r; + } + + spin_lock_irqsave(&repeater_lock, flags); + r = of_usb_find_repeater(node); + if (IS_ERR(r)) { + devres_free(r_devm); + goto err0; + } + + if (!try_module_get(r->dev->driver->owner)) { + r = ERR_PTR(-ENODEV); + devres_free(r_devm); + goto err0; + } + + devres_add(dev, r_devm); + get_device(r->dev); +err0: + spin_unlock_irqrestore(&repeater_lock, flags); + return r; +} +EXPORT_SYMBOL(devm_usb_get_repeater_by_node); + +struct usb_repeater *devm_usb_get_repeater_by_phandle(struct device *dev, + const char *phandle, u8 index) +{ + struct device_node *node; + struct usb_repeater *r; + + if (!dev->of_node) { + dev_dbg(dev, "device does not have a device node entry\n"); + return ERR_PTR(-EINVAL); + } + + node = of_parse_phandle(dev->of_node, phandle, index); + if (!node) { + dev_dbg(dev, "failed to get %s phandle in %pOF node\n", phandle, + dev->of_node); + return ERR_PTR(-ENODEV); + } + + r = devm_usb_get_repeater_by_node(dev, node); + of_node_put(node); + return r; +} +EXPORT_SYMBOL(devm_usb_get_repeater_by_phandle); + +/** + * usb_add_repeater_dev - Add repeater device + * @r: repeater device available + * + * It shall used by any USB repeater driver to register + * device to allow USB PHY driver to get access to it + * for invoking repeater specific operations. + */ +int usb_add_repeater_dev(struct usb_repeater *r) +{ + if (!r || !r->dev) { + pr_err("no repeater device\n"); + return -EINVAL; + } + + spin_lock(&repeater_lock); + list_add_tail(&r->head, &repeater_list); + spin_unlock(&repeater_lock); + + return 0; +} +EXPORT_SYMBOL(usb_add_repeater_dev); + +/** + * usb_remove_repeater_dev - remove repeater device + * @r: USB repeater device to remove from repeater list + */ +void usb_remove_repeater_dev(struct usb_repeater *r) +{ + unsigned long flags; + + spin_lock_irqsave(&repeater_lock, flags); + if (r) + list_del(&r->head); + spin_unlock_irqrestore(&repeater_lock, flags); +} +EXPORT_SYMBOL(usb_remove_repeater_dev); + +MODULE_DESCRIPTION("USB repeater framework"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/usb/repeater.h b/include/linux/usb/repeater.h new file mode 100644 index 000000000000..4cd34022391a --- /dev/null +++ b/include/linux/usb/repeater.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ +#ifndef __LINUX_USB_REPEATER_H +#define __LINUX_USB_REPEATER_H + +#include +#include +#include + +struct usb_repeater { + struct device *dev; + const char *label; + unsigned int flags; + + struct list_head head; + int (*reset)(struct usb_repeater *x, bool bring_out_of_reset); + int (*init)(struct usb_repeater *x); + int (*suspend)(struct usb_repeater *r, int suspend); + int (*powerup)(struct usb_repeater *r); + int (*powerdown)(struct usb_repeater *r); +}; + +#if IS_ENABLED(CONFIG_USB_REPEATER) +struct usb_repeater *devm_usb_get_repeater_by_phandle(struct device *dev, + const char *phandle, u8 index); +struct usb_repeater *devm_usb_get_repeater_by_node(struct device *dev, + struct device_node *node); +void usb_put_repeater(struct usb_repeater *r); +int usb_add_repeater_dev(struct usb_repeater *r); +void usb_remove_repeater_dev(struct usb_repeater *r); +#else +static inline struct usb_repeater *devm_usb_get_repeater_by_phandle( + struct device *d, const char *phandle, u8 index) +{ return ERR_PTR(-ENXIO); } + +static inline struct usb_repeater *devm_usb_get_repeater_by_node( + struct device *dev, struct device_node *node) +{ return ERR_PTR(-ENXIO); } + +static inline void usb_put_repeater(struct usb_repeater *r) +{ } + +static inline int usb_add_repeater_dev(struct usb_repeater *r) +{ return 0; } + +static inline void usb_remove_repeater_dev(struct usb_repeater *r) +{ } +#endif + +static inline int usb_repeater_reset(struct usb_repeater *r, + bool bring_out_of_reset) +{ + if (r && r->reset != NULL) + return r->reset(r, bring_out_of_reset); + else + return 0; +} + +static inline int usb_repeater_init(struct usb_repeater *r) +{ + if (r && r->init != NULL) + return r->init(r); + else + return 0; +} + +static inline int usb_repeater_suspend(struct usb_repeater *r, int suspend) +{ + if (r && r->suspend != NULL) + return r->suspend(r, suspend); + else + return 0; +} + +static inline int usb_repeater_powerup(struct usb_repeater *r) +{ + if (r && r->powerup != NULL) + return r->powerup(r); + else + return 0; +} + +static inline int usb_repeater_powerdown(struct usb_repeater *r) +{ + if (r && r->powerdown != NULL) + return r->powerdown(r); + else + return 0; +} +#endif /* __LINUX_USB_REPEATER_H */