diff --git a/Documentation/devicetree/bindings/phy/qcom,snps-eusb2-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,snps-eusb2-phy.yaml index 096f6b546632..1afb65820016 100644 --- a/Documentation/devicetree/bindings/phy/qcom,snps-eusb2-phy.yaml +++ b/Documentation/devicetree/bindings/phy/qcom,snps-eusb2-phy.yaml @@ -21,6 +21,7 @@ properties: - qcom,milos-snps-eusb2-phy - qcom,sar2130p-snps-eusb2-phy - qcom,sdx75-snps-eusb2-phy + - qcom,sm7675-snps-eusb2-phy - qcom,sm8650-snps-eusb2-phy - qcom,x1e80100-snps-eusb2-phy - const: qcom,sm8550-snps-eusb2-phy @@ -33,12 +34,16 @@ properties: const: 0 clocks: + minItems: 1 items: - description: ref + - description: Optional separate TCSR reference gate on Uke clock-names: + minItems: 1 items: - const: ref + - const: ref_gate resets: maxItems: 1 diff --git a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml index a7f58114c02e..c93e6bab7b3e 100644 --- a/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,dwc3.yaml @@ -67,6 +67,7 @@ properties: - qcom,sm8550-dwc3 - qcom,sm8650-dwc3 - qcom,sm8750-dwc3 + - qcom,sm7675-dwc3 - qcom,x1e80100-dwc3 - qcom,x1e80100-dwc3-mp - const: qcom,dwc3 diff --git a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml index 8201656b41ed..da90d3386166 100644 --- a/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/qcom,snps-dwc3.yaml @@ -65,6 +65,7 @@ properties: - qcom,sm6125-dwc3 - qcom,sm6350-dwc3 - qcom,sm6375-dwc3 + - qcom,sm7675-dwc3 - qcom,sm8150-dwc3 - qcom,sm8250-dwc3 - qcom,sm8350-dwc3 diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index abf8c6cdea9e..54353c8feaa2 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -160,3 +160,5 @@ source "drivers/usb/typec/Kconfig" source "drivers/usb/roles/Kconfig" endif # USB_SUPPORT + +source "drivers/usb/repeater/Kconfig" diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index eecbd631fdab..0bda2de29489 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -65,3 +65,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/common/Kconfig b/drivers/usb/common/Kconfig index b856622431a7..7233af11665a 100644 --- a/drivers/usb/common/Kconfig +++ b/drivers/usb/common/Kconfig @@ -49,3 +49,10 @@ config USB_CONN_GPIO To compile the driver as a module, choose M here: the module will be called usb-conn-gpio.ko + +config USB_WCD939X_UKE + bool "Xiaomi Uke WCD939x USB2 routing" + depends on I2C && REGULATOR && GPIOLIB + select REGMAP_I2C + help + Configure the fixed USB2 data route through the WCD939x on Xiaomi Uke. diff --git a/drivers/usb/common/Makefile b/drivers/usb/common/Makefile index 8ac4d21ef5c8..600e4d60e91d 100644 --- a/drivers/usb/common/Makefile +++ b/drivers/usb/common/Makefile @@ -11,3 +11,4 @@ usb-common-$(CONFIG_USB_LED_TRIG) += led.o obj-$(CONFIG_USB_CONN_GPIO) += usb-conn-gpio.o obj-$(CONFIG_USB_OTG_FSM) += usb-otg-fsm.o obj-$(CONFIG_USB_ULPI_BUS) += ulpi.o +obj-$(CONFIG_USB_WCD939X_UKE) += wcd939x-usb-uke.o diff --git a/drivers/usb/common/wcd939x-usb-uke.c b/drivers/usb/common/wcd939x-usb-uke.c new file mode 100644 index 000000000000..cbfa0c1d2cde --- /dev/null +++ b/drivers/usb/common/wcd939x-usb-uke.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Minimal WCD939x USB2 routing for Xiaomi Uke. + * + * Derived from the public Xiaomi WCD USBSS driver and the upstream + * wcd939x-usbss driver. Uke uses this chip between its eUSB2 PHY and the + * receptacle. This driver deliberately configures only the fixed USB2 + * peripheral route; Type-C alternate modes, audio accessory handling, UCSI, + * and charger detection are outside its scope. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WCD_USBSS_DP_DN_MISC1 0x20 +#define WCD_USBSS_DP_DN_DYN_BST (BIT(3) | BIT(0)) +#define WCD_USBSS_CPLDO_CTL2 0xa1 +#define WCD_USBSS_SWITCH_SETTINGS_ENABLE 0x403 +#define WCD_USBSS_DEVICE_ENABLE BIT(7) +#define WCD_USBSS_DNL_ENABLE BIT(4) +#define WCD_USBSS_DPR_ENABLE BIT(3) +#define WCD_USBSS_SWITCH_SELECT0 0x404 +#define WCD_USBSS_DNL_SELECT GENMASK(5, 4) +#define WCD_USBSS_DPR_SELECT GENMASK(3, 2) +#define WCD_USBSS_DNL_DN FIELD_PREP(WCD_USBSS_DNL_SELECT, 1) +#define WCD_USBSS_DPR_DP FIELD_PREP(WCD_USBSS_DPR_SELECT, 1) +#define WCD_USBSS_FUNCTION_ENABLE 0x413 +#define WCD_USBSS_MANUAL_MODE FIELD_PREP(GENMASK(1, 0), 1) +#define WCD_USBSS_EQUALIZER1 0x415 +#define WCD_USBSS_EQUALIZER_ENABLE BIT(7) +#define WCD_USBSS_EQUALIZER_BW GENMASK(6, 3) +#define WCD_USBSS_USB_SS_CNTL 0x419 +#define WCD_USBSS_STANDBY BIT(4) +#define WCD_USBSS_RCO_ENABLE BIT(3) +#define WCD_USBSS_USB_MODE GENMASK(2, 0) +#define WCD_USBSS_USB_MODE_5 FIELD_PREP(WCD_USBSS_USB_MODE, 5) +#define WCD_USBSS_FSM_OVERRIDE 0x63 +#define WCD_USBSS_DP_EN 0x1e +#define WCD_USBSS_DN_EN 0x21 +#define WCD_USBSS_MAX_REGISTER 0x4c1 + +struct wcd939x_usb_uke { + struct regulator *supply; + struct gpio_desc *reset; + struct regmap *regmap; + struct pinctrl *pinctrl; + struct pinctrl_state *active; +}; + +static const struct regmap_range_cfg wcd939x_usb_uke_ranges[] = { + { + .range_min = 0, + .range_max = WCD_USBSS_MAX_REGISTER, + .selector_reg = 0, + .selector_mask = 0xff, + .window_start = 0, + .window_len = 0x100, + }, +}; + +static const struct regmap_config wcd939x_usb_uke_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = WCD_USBSS_MAX_REGISTER, + .ranges = wcd939x_usb_uke_ranges, + .num_ranges = ARRAY_SIZE(wcd939x_usb_uke_ranges), +}; + +static int wcd939x_usb_uke_route(struct wcd939x_usb_uke *wcd, u32 eq_bw) +{ + int ret; + + ret = regmap_clear_bits(wcd->regmap, WCD_USBSS_USB_SS_CNTL, + WCD_USBSS_STANDBY); + if (ret) + return ret; + ret = regmap_update_bits(wcd->regmap, WCD_USBSS_FUNCTION_ENABLE, + GENMASK(1, 0), WCD_USBSS_MANUAL_MODE); + if (ret) + return ret; + ret = regmap_set_bits(wcd->regmap, WCD_USBSS_DP_DN_MISC1, + WCD_USBSS_DP_DN_DYN_BST); + if (ret) + return ret; + + /* Uke vendor OVP-fuse settings for the USB route. */ + ret = regmap_update_bits(wcd->regmap, WCD_USBSS_FSM_OVERRIDE, 0x77, 0x77); + if (ret) + return ret; + ret = regmap_update_bits(wcd->regmap, WCD_USBSS_DP_EN, 0x0e, 0x0c); + if (ret) + return ret; + ret = regmap_update_bits(wcd->regmap, WCD_USBSS_DN_EN, 0x0e, 0x0c); + if (ret) + return ret; + ret = regmap_set_bits(wcd->regmap, WCD_USBSS_CPLDO_CTL2, 0xff); + if (ret) + return ret; + + ret = regmap_clear_bits(wcd->regmap, WCD_USBSS_USB_SS_CNTL, + WCD_USBSS_RCO_ENABLE); + if (ret) + return ret; + ret = regmap_set_bits(wcd->regmap, WCD_USBSS_USB_SS_CNTL, + WCD_USBSS_RCO_ENABLE); + if (ret) + return ret; + ret = regmap_write(wcd->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE, + WCD_USBSS_DEVICE_ENABLE); + if (ret) + return ret; + ret = regmap_update_bits(wcd->regmap, WCD_USBSS_USB_SS_CNTL, + WCD_USBSS_USB_MODE, WCD_USBSS_USB_MODE_5); + if (ret) + return ret; + ret = regmap_update_bits(wcd->regmap, WCD_USBSS_SWITCH_SELECT0, + WCD_USBSS_DNL_SELECT | WCD_USBSS_DPR_SELECT, + WCD_USBSS_DNL_DN | WCD_USBSS_DPR_DP); + if (ret) + return ret; + ret = regmap_update_bits(wcd->regmap, WCD_USBSS_EQUALIZER1, + WCD_USBSS_EQUALIZER_ENABLE | WCD_USBSS_EQUALIZER_BW, + WCD_USBSS_EQUALIZER_ENABLE | + FIELD_PREP(WCD_USBSS_EQUALIZER_BW, eq_bw)); + if (ret) + return ret; + + return regmap_set_bits(wcd->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE, + WCD_USBSS_DNL_ENABLE | WCD_USBSS_DPR_ENABLE); +} + +static int wcd939x_usb_uke_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct wcd939x_usb_uke *wcd; + u32 eq_bw = 8; + int ret; + + wcd = devm_kzalloc(dev, sizeof(*wcd), GFP_KERNEL); + if (!wcd) + return -ENOMEM; + + wcd->regmap = devm_regmap_init_i2c(client, &wcd939x_usb_uke_regmap_config); + if (IS_ERR(wcd->regmap)) + return dev_err_probe(dev, PTR_ERR(wcd->regmap), "failed to initialize regmap\n"); + wcd->supply = devm_regulator_get(dev, "vdd-usb-cp"); + if (IS_ERR(wcd->supply)) + return dev_err_probe(dev, PTR_ERR(wcd->supply), "failed to get vdd-usb-cp\n"); + + wcd->pinctrl = devm_pinctrl_get(dev); + if (IS_ERR(wcd->pinctrl)) + return dev_err_probe(dev, PTR_ERR(wcd->pinctrl), "failed to get pinctrl\n"); + wcd->active = pinctrl_lookup_state(wcd->pinctrl, "usbss_active"); + if (IS_ERR(wcd->active)) + wcd->active = pinctrl_lookup_state(wcd->pinctrl, "active"); + if (IS_ERR(wcd->active)) + return dev_err_probe(dev, PTR_ERR(wcd->active), "failed to get active pins\n"); + ret = pinctrl_select_state(wcd->pinctrl, wcd->active); + if (ret) + return dev_err_probe(dev, ret, "failed to select active pins\n"); + + /* Select pins first, then assert reset using the DT active-low polarity. */ + wcd->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(wcd->reset)) + return dev_err_probe(dev, PTR_ERR(wcd->reset), "failed to get reset GPIO\n"); + + ret = regulator_enable(wcd->supply); + if (ret) + return dev_err_probe(dev, ret, "failed to enable vdd-usb-cp\n"); + msleep(20); + gpiod_set_value_cansleep(wcd->reset, 0); + msleep(20); + + of_property_read_u32(dev->of_node, "wcd-equ-bw-settings", &eq_bw); + if (eq_bw > 0xf) { + ret = -EINVAL; + dev_err(dev, "invalid equalizer bandwidth %u\n", eq_bw); + goto disable_supply; + } + ret = wcd939x_usb_uke_route(wcd, eq_bw); + if (ret) { + dev_err(dev, "failed to route USB2 D+/D-: %d\n", ret); + goto disable_supply; + } + + i2c_set_clientdata(client, wcd); + return 0; + +disable_supply: + gpiod_set_value_cansleep(wcd->reset, 1); + regulator_disable(wcd->supply); + return ret; +} + +static void wcd939x_usb_uke_remove(struct i2c_client *client) +{ + struct wcd939x_usb_uke *wcd = i2c_get_clientdata(client); + + regmap_clear_bits(wcd->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE, + WCD_USBSS_DNL_ENABLE | WCD_USBSS_DPR_ENABLE); + gpiod_set_value_cansleep(wcd->reset, 1); + regulator_disable(wcd->supply); +} + +static const struct of_device_id wcd939x_usb_uke_of_match[] = { + { .compatible = "qcom,sm7675-wcd939x-usb2" }, + { } +}; +MODULE_DEVICE_TABLE(of, wcd939x_usb_uke_of_match); + +static struct i2c_driver wcd939x_usb_uke_driver = { + .driver = { + .name = "wcd939x-usb-uke", + .of_match_table = wcd939x_usb_uke_of_match, + }, + .probe = wcd939x_usb_uke_probe, + .remove = wcd939x_usb_uke_remove, +}; +module_i2c_driver(wcd939x_usb_uke_driver); + +MODULE_DESCRIPTION("Xiaomi Uke WCD939x fixed USB2 routing driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index f43f73ac36ff..2c5232b227b3 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -69,6 +70,8 @@ struct dwc3_qcom_port { }; struct dwc3_qcom { + struct regulator *gdsc; + bool uke; struct device *dev; void __iomem *qscratch_base; struct dwc3 dwc; @@ -607,6 +610,49 @@ struct dwc3_glue_ops dwc3_qcom_glue_ops = { .pre_run_stop = dwc3_qcom_run_stop_notifier, }; +/* Uke retains firmware-owned USB power during the initial handoff. */ +static void dwc3_qcom_uke_power_off(void *data) +{ + struct dwc3_qcom *qcom = data; + + regulator_disable(qcom->gdsc); +} + +static int dwc3_qcom_uke_prepare(struct dwc3_qcom *qcom) +{ + int i, ret; + + qcom->gdsc = devm_regulator_get(qcom->dev, "gdsc"); + if (IS_ERR(qcom->gdsc)) + return dev_err_probe(qcom->dev, PTR_ERR(qcom->gdsc), "Missing USB GDSC\n"); + ret = regulator_enable(qcom->gdsc); + if (ret) + return ret; + ret = devm_add_action_or_reset(qcom->dev, dwc3_qcom_uke_power_off, qcom); + if (ret) + return ret; + + for (i = 0; i < qcom->num_clocks; i++) { + const char *name = qcom->clks[i].id; + u32 rate = 0; + + if (!name) + continue; + if (!strcmp(name, "core_clk")) + device_property_read_u32(qcom->dev, "qcom,core-clk-rate-disconnected", &rate); + else if (!strcmp(name, "sleep_clk")) + rate = 32000; + else if (!strcmp(name, "utmi_clk")) + rate = 19200000; + if (!rate) + continue; + ret = clk_set_rate(qcom->clks[i].clk, rate); + if (ret) + return dev_err_probe(qcom->dev, ret, "Failed USB clock rate\n"); + } + return 0; +} + static int dwc3_qcom_probe(struct platform_device *pdev) { struct dwc3_probe_data probe_data = {}; @@ -623,6 +669,7 @@ static int dwc3_qcom_probe(struct platform_device *pdev) return -ENOMEM; qcom->dev = &pdev->dev; + qcom->uke = of_device_is_compatible(dev->of_node, "qcom,sm7675-dwc3"); qcom->resets = devm_reset_control_array_get_optional_exclusive(dev); if (IS_ERR(qcom->resets)) { @@ -635,18 +682,18 @@ static int dwc3_qcom_probe(struct platform_device *pdev) return dev_err_probe(dev, ret, "failed to get clocks\n"); qcom->num_clocks = ret; - ret = reset_control_assert(qcom->resets); - if (ret) { - dev_err(&pdev->dev, "failed to assert resets, err=%d\n", ret); - return ret; - } - - usleep_range(10, 1000); - - ret = reset_control_deassert(qcom->resets); - if (ret) { - dev_err(&pdev->dev, "failed to deassert resets, err=%d\n", ret); - return ret; + if (qcom->uke) { + ret = dwc3_qcom_uke_prepare(qcom); + if (ret) + return ret; + } else { + ret = reset_control_assert(qcom->resets); + if (ret) + return dev_err_probe(dev, ret, "Failed to assert resets\n"); + usleep_range(10, 1000); + ret = reset_control_deassert(qcom->resets); + if (ret) + return dev_err_probe(dev, ret, "Failed to deassert resets\n"); } ret = clk_bulk_prepare_enable(qcom->num_clocks, qcom->clks); @@ -658,6 +705,10 @@ static int dwc3_qcom_probe(struct platform_device *pdev) ret = -EINVAL; goto clk_disable; } + if (resource_size(r) < SDM845_QSCRATCH_BASE_OFFSET + SDM845_QSCRATCH_SIZE) { + ret = -EINVAL; + goto clk_disable; + } res = *r; res.end = res.start + SDM845_QSCRATCH_BASE_OFFSET; @@ -840,6 +891,7 @@ static const struct dev_pm_ops dwc3_qcom_dev_pm_ops = { static const struct of_device_id dwc3_qcom_of_match[] = { { .compatible = "qcom,snps-dwc3" }, + { .compatible = "qcom,sm7675-dwc3" }, { } }; MODULE_DEVICE_TABLE(of, dwc3_qcom_of_match); diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 310b5ffb236a..a4cc6124b90a 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -27,6 +27,15 @@ #include "gadget.h" #include "io.h" +static void dwc3_ep0_set_state(struct dwc3 *dwc, enum usb_device_state state) +{ + if (state == USB_STATE_ADDRESS) + dwc3_gadget_diag_event(DWC3_DIAG_ADDRESS); + else if (state == USB_STATE_CONFIGURED) + dwc3_gadget_diag_event(DWC3_DIAG_CONFIGURED); + usb_gadget_set_state(dwc->gadget, state); +} + static void __dwc3_ep0_do_control_status(struct dwc3 *dwc, struct dwc3_ep *dep); static void __dwc3_ep0_do_control_data(struct dwc3 *dwc, struct dwc3_ep *dep, struct dwc3_request *req); @@ -134,7 +143,7 @@ static int __dwc3_gadget_ep0_queue(struct dwc3_ep *dep, direction = !dwc->ep0_expect_in; dwc->delayed_status = false; - usb_gadget_set_state(dwc->gadget, USB_STATE_CONFIGURED); + dwc3_ep0_set_state(dwc, USB_STATE_CONFIGURED); if (dwc->ep0state == EP0_STATUS_PHASE) __dwc3_ep0_do_control_status(dwc, dwc->eps[direction]); @@ -618,9 +627,9 @@ static int dwc3_ep0_set_address(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) dwc3_writel(dwc, DWC3_DCFG, reg); if (addr) - usb_gadget_set_state(dwc->gadget, USB_STATE_ADDRESS); + dwc3_ep0_set_state(dwc, USB_STATE_ADDRESS); else - usb_gadget_set_state(dwc->gadget, USB_STATE_DEFAULT); + dwc3_ep0_set_state(dwc, USB_STATE_DEFAULT); return 0; } @@ -665,7 +674,7 @@ static int dwc3_ep0_set_config(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) * to change the state on the next usb_ep_queue() */ if (ret == 0) - usb_gadget_set_state(dwc->gadget, + dwc3_ep0_set_state(dwc, USB_STATE_CONFIGURED); /* @@ -684,7 +693,7 @@ static int dwc3_ep0_set_config(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) case USB_STATE_CONFIGURED: ret = dwc3_ep0_delegate_req(dwc, ctrl); if (!cfg && !ret) - usb_gadget_set_state(dwc->gadget, + dwc3_ep0_set_state(dwc, USB_STATE_ADDRESS); break; default: @@ -830,11 +839,18 @@ static void dwc3_ep0_inspect_setup(struct dwc3 *dwc, int ret = -EINVAL; u32 len; + dwc3_gadget_diag_event(DWC3_DIAG_SETUP); + if (!dwc->gadget_driver || !dwc->softconnect || !dwc->connected) goto out; trace_dwc3_ctrl_req(dwc, ctrl); + if (ctrl->bRequestType == (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE) && + ctrl->bRequest == USB_REQ_GET_DESCRIPTOR && + (le16_to_cpu(ctrl->wValue) >> 8) == USB_DT_DEVICE) + dwc3_gadget_diag_event(DWC3_DIAG_DEVICE_DESC); + len = le16_to_cpu(ctrl->wLength); if (!len) { dwc->three_stage_setup = false; @@ -1187,7 +1203,7 @@ static void dwc3_ep0_xfernotready(struct dwc3 *dwc, */ if (!list_empty(&dep->pending_list)) { dwc->delayed_status = false; - usb_gadget_set_state(dwc->gadget, + dwc3_ep0_set_state(dwc, USB_STATE_CONFIGURED); dwc3_ep0_do_control_status(dwc, event); } diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index e46db7032089..03680d297db8 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -8,7 +8,9 @@ * Sebastian Andrzej Siewior */ +#include #include +#include #include #include #include @@ -20,6 +22,8 @@ #include #include +#include +#include #include #include "debug.h" @@ -27,6 +31,25 @@ #include "gadget.h" #include "io.h" +static atomic_t diag_events = ATOMIC_INIT(0); + +static int dwc3_diag_events_get(char *buffer, const struct kernel_param *kp) +{ + return scnprintf(buffer, PAGE_SIZE, "%u\n", + (unsigned int)atomic_read(kp->arg)); +} + +static const struct kernel_param_ops dwc3_diag_events_ops = { + .get = dwc3_diag_events_get, +}; +module_param_cb(diag_events, &dwc3_diag_events_ops, &diag_events, 0444); +MODULE_PARM_DESC(diag_events, "Latched USB bring-up events, cleared on module load"); + +void dwc3_gadget_diag_event(unsigned int event) +{ + atomic_or(event, &diag_events); +} + #define DWC3_ALIGN_FRAME(d, n) (((d)->frame_number + ((d)->interval * (n))) \ & ~((d)->interval - 1)) @@ -4103,6 +4126,21 @@ static void dwc3_clear_stall_all_ep(struct dwc3 *dwc) } } +/* Xiaomi dwc3-msm supplies cable notifications to its legacy USB PHY. + * The Uke callbacks only update state/MMIO and do not sleep. + */ +static void dwc3_uke_phy_notify(struct dwc3 *dwc, bool connected) +{ + if (!IS_ENABLED(CONFIG_USB_PHY_QCOM_UKE) || !dwc->usb2_phy || + !of_device_is_compatible(dwc->usb2_phy->dev->of_node, + "qcom,usb-snps-eusb2-phy")) + return; + if (connected) + usb_phy_notify_connect(dwc->usb2_phy, dwc->gadget->speed); + else + usb_phy_notify_disconnect(dwc->usb2_phy, dwc->gadget->speed); +} + static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc) { int reg; @@ -4120,6 +4158,8 @@ static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc) dwc3_disconnect_gadget(dwc); + dwc3_uke_phy_notify(dwc, false); + dwc->gadget->speed = USB_SPEED_UNKNOWN; dwc->setup_packet_pending = false; dwc->gadget->wakeup_armed = false; @@ -4140,6 +4180,8 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) { u32 reg; + dwc3_gadget_diag_event(DWC3_DIAG_RESET); + dwc->suspended = false; /* @@ -4222,6 +4264,8 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) u8 lanes = 1; u8 speed; + dwc3_gadget_diag_event(DWC3_DIAG_CONNECT_DONE); + if (!dwc->softconnect) return; @@ -4292,6 +4336,8 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) break; } + dwc3_uke_phy_notify(dwc, true); + dwc->eps[1]->endpoint.maxpacket = dwc->gadget->ep0->maxpacket; /* Enable USB2 LPM Capability */ diff --git a/drivers/usb/dwc3/gadget.h b/drivers/usb/dwc3/gadget.h index 45f113b3c146..4b56963fb37c 100644 --- a/drivers/usb/dwc3/gadget.h +++ b/drivers/usb/dwc3/gadget.h @@ -16,6 +16,17 @@ #include "io.h" struct dwc3; + +/* Latched bring-up evidence for Uke's single DWC3 controller. */ +#define DWC3_DIAG_RESET BIT(0) +#define DWC3_DIAG_CONNECT_DONE BIT(1) +#define DWC3_DIAG_SETUP BIT(2) +#define DWC3_DIAG_DEVICE_DESC BIT(3) +#define DWC3_DIAG_ADDRESS BIT(4) +#define DWC3_DIAG_CONFIGURED BIT(5) + +void dwc3_gadget_diag_event(unsigned int event); + #define to_dwc3_ep(ep) (container_of(ep, struct dwc3_ep, endpoint)) #define gadget_to_dwc(g) (dev_get_platdata(&g->dev)) diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index b7acf3966cd7..802b6182d4c2 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -161,3 +161,9 @@ config USB_ULPI_VIEWPORT controllers with a viewport register (e.g. Chipidea/ARC controllers). endmenu + +config USB_PHY_QCOM_UKE + tristate "Uke public Xiaomi SNPS eUSB2 PHY" + depends on OF && REGULATOR && (ARCH_QCOM || COMPILE_TEST) + select USB_PHY + select USB_REPEATER diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index ceae46c5341d..1d151ff7a8fb 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile @@ -22,3 +22,5 @@ obj-$(CONFIG_USB_MXS_PHY) += phy-mxs-usb.o obj-$(CONFIG_USB_ULPI) += phy-ulpi.o obj-$(CONFIG_USB_ULPI_VIEWPORT) += phy-ulpi-viewport.o obj-$(CONFIG_KEYSTONE_USB_PHY) += phy-keystone.o + +obj-$(CONFIG_USB_PHY_QCOM_UKE) += phy-msm-snps-eusb2-uke.o diff --git a/drivers/usb/phy/phy-msm-snps-eusb2-uke.c b/drivers/usb/phy/phy-msm-snps-eusb2-uke.c new file mode 100644 index 000000000000..8dc00f3a6fea --- /dev/null +++ b/drivers/usb/phy/phy-msm-snps-eusb2-uke.c @@ -0,0 +1,927 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021-2023, Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2023-2024 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "eusb2_phy: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Flag values from public Xiaomi include/linux/usb/dwc3-msm.h. */ +#define PHY_HOST_MODE BIT(0) +#define EUD_SPOOF_DISCONNECT BIT(6) + +#define USB_PHY_UTMI_CTRL0 (0x3c) +#define OPMODE_MASK (0x3 << 3) +#define OPMODE_NONDRIVING (0x1 << 3) +#define SLEEPM BIT(0) + +#define USB_PHY_UTMI_CTRL5 (0x50) +#define POR BIT(1) + +#define USB_PHY_HS_PHY_CTRL_COMMON0 (0x54) +#define PHY_ENABLE BIT(0) +#define SIDDQ_SEL BIT(1) +#define SIDDQ BIT(2) +#define RETENABLEN BIT(3) +#define FSEL (0x7 << 4) +#define FSEL_19_2_MHZ_VAL (0x0 << 4) +#define FSEL_38_4_MHZ_VAL (0x4 << 4) + +#define USB_PHY_CFG_CTRL_1 (0x58) +#define PHY_CFG_PLL_CPBIAS_CNTRL (0xfe) +#define PHY_CFG_PLL_CPBIAS_CNTRL_SHIFT (0x1) + +#define USB_PHY_CFG_CTRL_2 (0x5c) +#define PHY_CFG_PLL_FB_DIV_7_0 (0xff) +#define DIV_7_0_19_2_MHZ_VAL (0x90) +#define DIV_7_0_38_4_MHZ_VAL (0xc8) + +#define USB_PHY_CFG_CTRL_3 (0x60) +#define PHY_CFG_PLL_FB_DIV_11_8 (0xf) +#define DIV_11_8_19_2_MHZ_VAL (0x1) +#define DIV_11_8_38_4_MHZ_VAL (0x0) + +#define PHY_CFG_PLL_REF_DIV (0xf << 4) +#define PLL_REF_DIV_VAL (0x0) + +#define USB_PHY_HS_PHY_CTRL2 (0x64) +#define VBUSVLDEXT0 BIT(0) +#define USB2_SUSPEND_N BIT(2) +#define USB2_SUSPEND_N_SEL BIT(3) +#define VBUS_DET_EXT_SEL BIT(4) + +#define USB_PHY_CFG_CTRL_4 (0x68) +#define PHY_CFG_PLL_GMP_CNTRL (0x3) +#define PHY_CFG_PLL_GMP_CNTRL_SHIFT (0x0) +#define PHY_CFG_PLL_INT_CNTRL (0xfc) +#define PHY_CFG_PLL_INT_CNTRL_SHIFT (0x2) + +#define USB_PHY_CFG_CTRL_5 (0x6c) +#define PHY_CFG_PLL_PROP_CNTRL (0x1f) +#define PHY_CFG_PLL_PROP_CNTRL_SHIFT (0x0) +#define PHY_CFG_PLL_VREF_TUNE (0x3 << 6) +#define PHY_CFG_PLL_VREF_TUNE_SHIFT (6) + +#define USB_PHY_CFG_CTRL_6 (0x70) +#define PHY_CFG_PLL_VCO_CNTRL (0x7) +#define PHY_CFG_PLL_VCO_CNTRL_SHIFT (0x0) + +#define USB_PHY_CFG_CTRL_7 (0x74) + +#define USB_PHY_CFG_CTRL_8 (0x78) +#define PHY_CFG_TX_FSLS_VREF_TUNE (0x3) +#define PHY_CFG_TX_FSLS_VREG_BYPASS BIT(2) +#define PHY_CFG_TX_HS_VREF_TUNE (0x7 << 3) +#define PHY_CFG_TX_HS_VREF_TUNE_SHIFT (0x3) +#define PHY_CFG_TX_HS_XV_TUNE (0x3 << 6) +#define PHY_CFG_TX_HS_XV_TUNE_SHIFT (6) + +#define USB_PHY_CFG_CTRL_9 (0x7c) +#define PHY_CFG_TX_PREEMP_TUNE (0x7) +#define PHY_CFG_TX_PREEMP_TUNE_SHIFT (0x0) +#define PHY_CFG_TX_RES_TUNE (0x3 << 3) +#define PHY_CFG_TX_RES_TUNE_SHIFT (0x3) +#define PHY_CFG_TX_RISE_TUNE (0x3 << 5) +#define PHY_CFG_TX_RISE_TUNE_SHIFT (0x5) +#define PHY_CFG_RCAL_BYPASS BIT(7) +#define PHY_CFG_RCAL_BYPASS_SHIFT (0x7) + +#define USB_PHY_CFG_CTRL_10 (0x80) + +#define USB_PHY_CFG0 (0x94) +#define DATAPATH_CTRL_OVERRIDE_EN BIT(0) +#define CMN_CTRL_OVERRIDE_EN BIT(1) + +#define UTMI_PHY_CMN_CTRL0 (0x98) +#define TESTBURNIN BIT(6) + +#define USB_PHY_FSEL_SEL (0xb8) +#define FSEL_SEL BIT(0) + +#define USB_PHY_APB_ACCESS_CMD (0x130) +#define RW_ACCESS BIT(0) +#define APB_START_CMD BIT(1) +#define APB_LOGIC_RESET BIT(2) + +#define USB_PHY_APB_ACCESS_STATUS (0x134) +#define ACCESS_DONE BIT(0) +#define TIMED_OUT BIT(1) +#define ACCESS_ERROR BIT(2) +#define ACCESS_IN_PROGRESS BIT(3) + +#define USB_PHY_APB_ADDRESS (0x138) +#define APB_REG_ADDR (0xff) + +#define USB_PHY_APB_WRDATA_LSB (0x13c) +#define APB_REG_WRDATA_7_0 (0xf) + +#define USB_PHY_APB_WRDATA_MSB (0x140) +#define APB_REG_WRDATA_15_8 (0xf0) + +#define USB_PHY_APB_RDDATA_LSB (0x144) +#define APB_REG_RDDATA_7_0 (0xf) + +#define USB_PHY_APB_RDDATA_MSB (0x148) +#define APB_REG_RDDATA_15_8 (0xf0) + +/* EUD CSR field */ +#define EUD_EN2 BIT(0) + +/* VIOCTL_EUD_DETECT register based EUD_DETECT field */ +#define EUD_DETECT BIT(0) + +#define USB_HSPHY_1P2_VOL_MIN 1200000 /* uV */ +#define USB_HSPHY_1P2_VOL_MAX 1200000 /* uV */ +#define USB_HSPHY_1P2_HPM_LOAD 5905 /* uA */ +#define USB_HSPHY_VDD_HPM_LOAD 7757 /* uA */ + +struct msm_eusb2_phy { + struct usb_phy phy; + void __iomem *base; + + /* EUD related parameters */ + void __iomem *eud_enable_reg; + void __iomem *eud_detect_reg; + + struct clk *ref_clk_src; + struct clk *ref_clk; + struct reset_control *phy_reset; + + struct regulator *vdd; + struct regulator *vdda12; + int vdd_levels[3]; /* none, low, high */ + + bool clocks_enabled; + bool power_enabled; + int power_error; + int init_error; + int io_error; + bool suspended; + bool cable_connected; + + int *param_override_seq; + int param_override_seq_cnt; + + struct usb_repeater *ur; +}; + +static inline bool is_eud_debug_mode_active(struct msm_eusb2_phy *phy) +{ + if (phy->eud_enable_reg && + (readl_relaxed(phy->eud_enable_reg) & EUD_EN2)) + return true; + + return false; +} + +static int msm_eusb2_phy_clocks(struct msm_eusb2_phy *phy, bool on) +{ + int ret; + + if (phy->clocks_enabled == on) + return 0; + if (on) { + ret = clk_prepare_enable(phy->ref_clk_src); + if (ret) + return ret; + ret = clk_prepare_enable(phy->ref_clk); + if (ret) { + clk_disable_unprepare(phy->ref_clk_src); + return ret; + } + } else { + clk_disable_unprepare(phy->ref_clk); + clk_disable_unprepare(phy->ref_clk_src); + } + phy->clocks_enabled = on; + return 0; +} + +static void msm_eusb2_phy_update_eud_detect(struct msm_eusb2_phy *phy, bool set) +{ + if (set) + writel_relaxed(EUD_DETECT, phy->eud_detect_reg); + else + writel_relaxed(readl_relaxed(phy->eud_detect_reg) & ~EUD_DETECT, + phy->eud_detect_reg); +} + +static int msm_eusb2_phy_power_impl(struct msm_eusb2_phy *phy, bool on) +{ + int ret = 0; + + dev_dbg(phy->phy.dev, "turn %s regulators. power_enabled:%d\n", + on ? "on" : "off", phy->power_enabled); + + if (phy->power_enabled == on) + return 0; + + if (!on) + goto clear_eud_det; + + ret = regulator_set_load(phy->vdd, USB_HSPHY_VDD_HPM_LOAD); + if (ret < 0) { + dev_err(phy->phy.dev, "Unable to set HPM of vdd:%d\n", ret); + goto err_vdd; + } + + ret = regulator_set_voltage(phy->vdd, phy->vdd_levels[1], + phy->vdd_levels[2]); + if (ret) { + dev_err(phy->phy.dev, "Unable to set voltage for hsusb vdd\n"); + goto put_vdd_lpm; + } + + ret = regulator_enable(phy->vdd); + if (ret) { + dev_err(phy->phy.dev, "Unable to enable VDD\n"); + goto unconfig_vdd; + } + + ret = regulator_set_load(phy->vdda12, USB_HSPHY_1P2_HPM_LOAD); + if (ret < 0) { + dev_err(phy->phy.dev, "Unable to set HPM of vdda12:%d\n", ret); + goto disable_vdd; + } + + ret = regulator_set_voltage(phy->vdda12, USB_HSPHY_1P2_VOL_MIN, + USB_HSPHY_1P2_VOL_MAX); + if (ret) { + dev_err(phy->phy.dev, + "Unable to set voltage for vdda12:%d\n", ret); + goto put_vdda12_lpm; + } + + ret = regulator_enable(phy->vdda12); + if (ret) { + dev_err(phy->phy.dev, "Unable to enable vdda12:%d\n", ret); + goto unset_vdda12; + } + + /* Make sure all the writes are processed before setting EUD_DETECT */ + mb(); + /* Set eud_detect_reg after powering on eUSB PHY rails to bring EUD out of reset */ + msm_eusb2_phy_update_eud_detect(phy, true); + + phy->power_enabled = true; + pr_debug("eUSB2_PHY's regulators are turned ON.\n"); + return ret; + +clear_eud_det: + /* Clear eud_detect_reg to put EUD in reset */ + msm_eusb2_phy_update_eud_detect(phy, false); + + /* Make sure clearing EUD_DETECT is completed before turning off the regulators */ + mb(); + + ret = regulator_disable(phy->vdda12); + if (ret) + return ret; + +unset_vdda12: + ret = regulator_set_voltage(phy->vdda12, 0, USB_HSPHY_1P2_VOL_MAX); + if (ret < 0) + return ret; + +put_vdda12_lpm: + ret = regulator_set_load(phy->vdda12, 0); + if (ret < 0) + return ret; + +disable_vdd: + ret = regulator_disable(phy->vdd); + if (ret) + return ret; + +unconfig_vdd: + ret = regulator_set_voltage(phy->vdd, phy->vdd_levels[0], + phy->vdd_levels[2]); + if (ret < 0) + return ret; + +put_vdd_lpm: + ret = regulator_set_load(phy->vdd, 0); + if (ret < 0) + return ret; + + /* case handling when regulator turning on failed */ + if (!phy->power_enabled) + return -EINVAL; + +err_vdd: + phy->power_enabled = false; + dev_dbg(phy->phy.dev, "eusb2_PHY's regulators are turned OFF.\n"); + return ret; +} + +static int msm_eusb2_phy_power(struct msm_eusb2_phy *phy, bool on) +{ + int ret; + + if (phy->power_error) + return phy->power_error; + ret = msm_eusb2_phy_power_impl(phy, on); + if (ret) { + phy->power_error = ret; + dev_err(phy->phy.dev, + "power transaction failed: %d; further power changes blocked\n", ret); + } + return ret; +} + +static void msm_eusb2_write_readback(struct msm_eusb2_phy *phy, u32 offset, + const u32 mask, u32 val) +{ + u32 write_val, tmp; + void __iomem *base = phy->base; + + if (phy->io_error) + return; + tmp = readl_relaxed(base + offset); + + tmp &= ~mask; + write_val = tmp | val; + + writel_relaxed(write_val, base + offset); + + /* Read back to see if val was written */ + tmp = readl_relaxed(base + offset); + tmp &= mask; + + if (tmp != val) { + phy->io_error = -EIO; + pr_err("write: %x to offset: %x FAILED\n", val, offset); + } +} + +static int msm_eusb2_phy_reset(struct msm_eusb2_phy *phy) +{ + int ret = reset_control_assert(phy->phy_reset); + + if (ret) + return ret; + usleep_range(100, 150); + return reset_control_deassert(phy->phy_reset); +} + +static void eusb2_phy_write_seq(struct msm_eusb2_phy *phy, u32 *seq, int cnt) +{ + int i; + + pr_debug("Seq count:%d\n", cnt); + for (i = 0; i < cnt; i = i+2) { + pr_debug("write 0x%02x to 0x%02x\n", seq[i], seq[i+1]); + if (phy->io_error) + return; + writel_relaxed(seq[i], phy->base + seq[i+1]); + if (readl_relaxed(phy->base + seq[i+1]) != seq[i]) + phy->io_error = -EIO; + } +} + +static void msm_eusb2_parameter_override(struct msm_eusb2_phy *phy) +{ + /* default parameters: tx pre-emphasis */ + msm_eusb2_write_readback(phy, USB_PHY_CFG_CTRL_9, + PHY_CFG_TX_PREEMP_TUNE, (0 << PHY_CFG_TX_PREEMP_TUNE_SHIFT)); + + /* tx rise/fall time */ + msm_eusb2_write_readback(phy, USB_PHY_CFG_CTRL_9, + PHY_CFG_TX_RISE_TUNE, (0x2 << PHY_CFG_TX_RISE_TUNE_SHIFT)); + + /* source impedance adjustment */ + msm_eusb2_write_readback(phy, USB_PHY_CFG_CTRL_9, + PHY_CFG_TX_RES_TUNE, (0x1 << PHY_CFG_TX_RES_TUNE_SHIFT)); + + /* dc voltage level adjustement */ + msm_eusb2_write_readback(phy, USB_PHY_CFG_CTRL_8, + PHY_CFG_TX_HS_VREF_TUNE, (0x3 << PHY_CFG_TX_HS_VREF_TUNE_SHIFT)); + + /* transmitter HS crossover adjustement */ + msm_eusb2_write_readback(phy, USB_PHY_CFG_CTRL_8, + PHY_CFG_TX_HS_XV_TUNE, (0x0 << PHY_CFG_TX_HS_XV_TUNE_SHIFT)); + + /* override init sequence using devicetree based values */ + eusb2_phy_write_seq(phy, phy->param_override_seq, + phy->param_override_seq_cnt); + + +} + +static void msm_eusb2_ref_clk_init(struct usb_phy *uphy) +{ + unsigned long ref_clk_freq; + struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy); + + ref_clk_freq = clk_get_rate(phy->ref_clk_src); + switch (ref_clk_freq) { + case 19200000: + msm_eusb2_write_readback(phy, USB_PHY_HS_PHY_CTRL_COMMON0, + FSEL, FSEL_19_2_MHZ_VAL); + + msm_eusb2_write_readback(phy, USB_PHY_CFG_CTRL_2, + PHY_CFG_PLL_FB_DIV_7_0, DIV_7_0_19_2_MHZ_VAL); + + msm_eusb2_write_readback(phy, USB_PHY_CFG_CTRL_3, + PHY_CFG_PLL_FB_DIV_11_8, DIV_11_8_19_2_MHZ_VAL); + break; + case 38400000: + msm_eusb2_write_readback(phy, USB_PHY_HS_PHY_CTRL_COMMON0, + FSEL, FSEL_38_4_MHZ_VAL); + + msm_eusb2_write_readback(phy, USB_PHY_CFG_CTRL_2, + PHY_CFG_PLL_FB_DIV_7_0, DIV_7_0_38_4_MHZ_VAL); + + msm_eusb2_write_readback(phy, USB_PHY_CFG_CTRL_3, + PHY_CFG_PLL_FB_DIV_11_8, DIV_11_8_38_4_MHZ_VAL); + break; + default: + dev_err(uphy->dev, "unsupported ref_clk_freq:%lu\n", + ref_clk_freq); + } + + msm_eusb2_write_readback(phy, USB_PHY_CFG_CTRL_3, + PHY_CFG_PLL_REF_DIV, PLL_REF_DIV_VAL); +} + +static int msm_eusb2_repeater_reset_and_init(struct msm_eusb2_phy *phy) +{ + int ret; + + phy->ur->flags = phy->phy.flags; + ret = usb_repeater_powerup(phy->ur); + if (ret) + return ret; + ret = usb_repeater_reset(phy->ur, true); + if (!ret) + ret = usb_repeater_init(phy->ur); + if (ret) { + usb_repeater_reset(phy->ur, false); + usb_repeater_powerdown(phy->ur); + } + return ret; +} + +static unsigned int diag_init; +module_param_named(diag_init, diag_init, uint, 0444); + +static int msm_eusb2_phy_init_impl(struct usb_phy *uphy); +static int msm_eusb2_phy_init(struct usb_phy *uphy) +{ + int ret; + + WRITE_ONCE(diag_init, 10000 + EINPROGRESS); + ret = msm_eusb2_phy_init_impl(uphy); + container_of(uphy, struct msm_eusb2_phy, phy)->init_error = ret; + if (ret) { + struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy); + + if (!is_eud_debug_mode_active(phy)) { + msm_eusb2_phy_clocks(phy, false); + if (!msm_eusb2_phy_power(phy, false)) { + usb_repeater_reset(phy->ur, false); + usb_repeater_powerdown(phy->ur); + } + } + } + WRITE_ONCE(diag_init, (READ_ONCE(diag_init) / 10000) * 10000 - ret); + return ret; +} + +static int msm_eusb2_phy_init_impl(struct usb_phy *uphy) +{ + struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy); + int ret; + + dev_info(uphy->dev, "phy_flags:%x\n", phy->phy.flags); + if (phy->phy.flags & PHY_HOST_MODE) + return -EOPNOTSUPP; + if (is_eud_debug_mode_active(phy)) { + ret = msm_eusb2_phy_power(phy, true); + if (ret) + return ret; + ret = msm_eusb2_phy_clocks(phy, true); + if (ret) + return ret; + return msm_eusb2_repeater_reset_and_init(phy); + } + + WRITE_ONCE(diag_init, 2 * 10000 + EINPROGRESS); + ret = msm_eusb2_phy_power(phy, true); + if (ret) + return ret; + + /* Bring eUSB2 repeater out of reset and initialized before eUSB2 PHY */ + WRITE_ONCE(diag_init, 3 * 10000 + EINPROGRESS); + ret = msm_eusb2_repeater_reset_and_init(phy); + if (ret) + return ret; + + WRITE_ONCE(diag_init, 4 * 10000 + EINPROGRESS); + ret = msm_eusb2_phy_clocks(phy, true); + if (ret) + return ret; + + WRITE_ONCE(diag_init, 5 * 10000 + EINPROGRESS); + ret = msm_eusb2_phy_reset(phy); + if (ret) + return ret; + + msm_eusb2_write_readback(phy, USB_PHY_CFG0, + CMN_CTRL_OVERRIDE_EN, CMN_CTRL_OVERRIDE_EN); + + msm_eusb2_write_readback(phy, USB_PHY_UTMI_CTRL5, POR, POR); + + msm_eusb2_write_readback(phy, USB_PHY_HS_PHY_CTRL_COMMON0, + PHY_ENABLE | RETENABLEN, PHY_ENABLE | RETENABLEN); + + msm_eusb2_write_readback(phy, USB_PHY_APB_ACCESS_CMD, + APB_LOGIC_RESET, APB_LOGIC_RESET); + + msm_eusb2_write_readback(phy, UTMI_PHY_CMN_CTRL0, TESTBURNIN, 0); + + msm_eusb2_write_readback(phy, USB_PHY_FSEL_SEL, + FSEL_SEL, FSEL_SEL); + + /* update ref_clk related registers */ + WRITE_ONCE(diag_init, 6 * 10000 + EINPROGRESS); + msm_eusb2_ref_clk_init(uphy); + + msm_eusb2_write_readback(phy, USB_PHY_CFG_CTRL_1, + PHY_CFG_PLL_CPBIAS_CNTRL, + (0x1 << PHY_CFG_PLL_CPBIAS_CNTRL_SHIFT)); + + msm_eusb2_write_readback(phy, USB_PHY_CFG_CTRL_4, + PHY_CFG_PLL_INT_CNTRL, + (0x8 << PHY_CFG_PLL_INT_CNTRL_SHIFT)); + + msm_eusb2_write_readback(phy, USB_PHY_CFG_CTRL_4, + PHY_CFG_PLL_GMP_CNTRL, + (0x1 << PHY_CFG_PLL_GMP_CNTRL_SHIFT)); + + msm_eusb2_write_readback(phy, USB_PHY_CFG_CTRL_5, + PHY_CFG_PLL_PROP_CNTRL, + (0x10 << PHY_CFG_PLL_PROP_CNTRL_SHIFT)); + + msm_eusb2_write_readback(phy, USB_PHY_CFG_CTRL_6, + PHY_CFG_PLL_VCO_CNTRL, + (0x0 << PHY_CFG_PLL_VCO_CNTRL_SHIFT)); + + msm_eusb2_write_readback(phy, USB_PHY_CFG_CTRL_5, + PHY_CFG_PLL_VREF_TUNE, + (0x1 << PHY_CFG_PLL_VREF_TUNE_SHIFT)); + + msm_eusb2_write_readback(phy, USB_PHY_HS_PHY_CTRL2, + VBUS_DET_EXT_SEL, VBUS_DET_EXT_SEL); + + /* set parameter override if needed */ + WRITE_ONCE(diag_init, 7 * 10000 + EINPROGRESS); + msm_eusb2_parameter_override(phy); + + msm_eusb2_write_readback(phy, USB_PHY_HS_PHY_CTRL2, + USB2_SUSPEND_N_SEL | USB2_SUSPEND_N, + USB2_SUSPEND_N_SEL | USB2_SUSPEND_N); + + msm_eusb2_write_readback(phy, USB_PHY_UTMI_CTRL0, SLEEPM, SLEEPM); + + msm_eusb2_write_readback(phy, USB_PHY_HS_PHY_CTRL_COMMON0, + SIDDQ_SEL, SIDDQ_SEL); + + msm_eusb2_write_readback(phy, USB_PHY_HS_PHY_CTRL_COMMON0, + SIDDQ, 0); + + msm_eusb2_write_readback(phy, USB_PHY_UTMI_CTRL5, POR, 0); + + msm_eusb2_write_readback(phy, USB_PHY_HS_PHY_CTRL2, + USB2_SUSPEND_N_SEL, 0); + + msm_eusb2_write_readback(phy, USB_PHY_CFG0, + CMN_CTRL_OVERRIDE_EN, 0x00); + + WRITE_ONCE(diag_init, 8 * 10000 + EINPROGRESS); + return phy->io_error; +} + +static int msm_eusb2_phy_set_suspend(struct usb_phy *uphy, int suspend) +{ + struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy); + + int ret; + + if (!suspend && phy->init_error) + return phy->init_error; + + if (phy->suspended && suspend) { + dev_dbg(uphy->dev, "USB PHY is already suspended\n"); + return 0; + } + + dev_dbg(uphy->dev, "phy->flags:0x%x\n", phy->phy.flags); + if (suspend) { + /* Bus suspend handling */ + if (phy->cable_connected || + (phy->phy.flags & PHY_HOST_MODE)) { + msm_eusb2_phy_clocks(phy, false); + goto suspend_exit; + } + + /* With EUD spoof disconnect, keep clk and ldos on */ + if ((phy->phy.flags & EUD_SPOOF_DISCONNECT) || is_eud_debug_mode_active(phy)) + goto suspend_exit; + + msm_eusb2_phy_clocks(phy, false); + ret = msm_eusb2_phy_power(phy, false); + if (ret) + return ret; + + /* Hold repeater into reset after powering down PHY */ + ret = usb_repeater_reset(phy->ur, false); + if (ret) + return ret; + ret = usb_repeater_powerdown(phy->ur); + if (ret) + return ret; + } else { + /* Bus resume and cable connect handling */ + ret = msm_eusb2_phy_power(phy, true); + if (ret) + return ret; + ret = msm_eusb2_phy_clocks(phy, true); + if (ret) + return ret; + } + +suspend_exit: + phy->suspended = !!suspend; + return 0; +} + +static int msm_eusb2_phy_notify_connect(struct usb_phy *uphy, + enum usb_device_speed speed) +{ + struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy); + + if (speed > USB_SPEED_HIGH) + return -EOPNOTSUPP; + phy->cable_connected = true; + return 0; +} + +static int msm_eusb2_phy_notify_disconnect(struct usb_phy *uphy, + enum usb_device_speed speed) +{ + struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy); + + if (is_eud_debug_mode_active(phy) && !(phy->phy.flags & EUD_SPOOF_DISCONNECT)) { + msm_eusb2_phy_update_eud_detect(phy, false); + /* Ensure that EUD disable occurs before re-enabling */ + mb(); + msm_eusb2_phy_update_eud_detect(phy, true); + } + + phy->cable_connected = false; + return 0; +} + +static unsigned int diag_probe; +module_param_named(diag_probe, diag_probe, uint, 0444); + +static int msm_eusb2_phy_probe_impl(struct platform_device *pdev); +static int msm_eusb2_phy_probe(struct platform_device *pdev) +{ + int ret; + + WRITE_ONCE(diag_probe, 10000 + EINPROGRESS); + ret = msm_eusb2_phy_probe_impl(pdev); + WRITE_ONCE(diag_probe, (READ_ONCE(diag_probe) / 10000) * 10000 - ret); + return ret; +} + +static int msm_eusb2_phy_probe_impl(struct platform_device *pdev) +{ + struct msm_eusb2_phy *phy; + struct device *dev = &pdev->dev; + struct resource *res; + int ret = 0; + struct usb_repeater *ur = NULL; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) { + ret = -ENOMEM; + goto err_ret; + } + WRITE_ONCE(diag_probe, 2 * 10000 + EINPROGRESS); + ur = devm_usb_get_repeater_by_phandle(dev, "usb-repeater", 0); + if (IS_ERR(ur)) { + ret = PTR_ERR(ur); + goto err_ret; + } + + WRITE_ONCE(diag_probe, 3 * 10000 + EINPROGRESS); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "eusb2_phy_base"); + if (!res) { + dev_err(dev, "missing eusb2phy memory resource\n"); + ret = -ENODEV; + goto err_ret; + } + + phy->base = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->base)) { + dev_err(dev, "ioremap failed\n"); + ret = -ENODEV; + goto err_ret; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "eud_enable_reg"); + if (res) { + phy->eud_enable_reg = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->eud_enable_reg)) { + ret = PTR_ERR(phy->eud_enable_reg); + dev_err(dev, "eud_enable_reg ioremap err:%d\n", ret); + goto err_ret; + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "eud_detect_reg"); + if (!res) { + dev_err(dev, "missing eud_detect register address\n"); + ret = -ENODEV; + goto err_ret; + } + + phy->eud_detect_reg = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->eud_detect_reg)) { + ret = PTR_ERR(phy->eud_detect_reg); + dev_err(dev, "eud_detect_reg ioremap err:%d\n", ret); + goto err_ret; + } + + WRITE_ONCE(diag_probe, 4 * 10000 + EINPROGRESS); + phy->ref_clk_src = devm_clk_get(dev, "ref_clk_src"); + if (IS_ERR(phy->ref_clk_src)) { + dev_err(dev, "clk get failed for ref_clk_src\n"); + ret = PTR_ERR(phy->ref_clk_src); + goto err_ret; + } + + phy->ref_clk = devm_clk_get(dev, "ref_clk"); + if (IS_ERR(phy->ref_clk)) { + dev_err(dev, "clk get failed for ref_clk\n"); + ret = PTR_ERR(phy->ref_clk); + goto err_ret; + } + + if (clk_get_rate(phy->ref_clk_src) != 19200000 && + clk_get_rate(phy->ref_clk_src) != 38400000) + return -EINVAL; + + WRITE_ONCE(diag_probe, 5 * 10000 + EINPROGRESS); + phy->phy_reset = devm_reset_control_get(dev, "phy_reset"); + if (IS_ERR(phy->phy_reset)) { + ret = PTR_ERR(phy->phy_reset); + goto err_ret; + } + + ret = of_property_read_u32_array(dev->of_node, "qcom,vdd-voltage-level", + (u32 *) phy->vdd_levels, + ARRAY_SIZE(phy->vdd_levels)); + if (ret) { + dev_err(dev, "error reading qcom,vdd-voltage-level property\n"); + goto err_ret; + } + + WRITE_ONCE(diag_probe, 6 * 10000 + EINPROGRESS); + phy->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(phy->vdd)) { + dev_err(dev, "unable to get vdd supply\n"); + ret = PTR_ERR(phy->vdd); + goto err_ret; + } + + phy->vdda12 = devm_regulator_get(dev, "vdda12"); + if (IS_ERR(phy->vdda12)) { + dev_err(dev, "unable to get vdda12 supply\n"); + ret = PTR_ERR(phy->vdda12); + goto err_ret; + } + + WRITE_ONCE(diag_probe, 7 * 10000 + EINPROGRESS); + phy->param_override_seq_cnt = of_property_count_elems_of_size( + dev->of_node, "qcom,param-override-seq", + sizeof(*phy->param_override_seq)); + if (phy->param_override_seq_cnt % 2) { + dev_err(dev, "invalid param_override_seq_len\n"); + ret = -EINVAL; + goto err_ret; + } + + if (phy->param_override_seq_cnt > 0) { + phy->param_override_seq = devm_kcalloc(dev, + phy->param_override_seq_cnt, + sizeof(*phy->param_override_seq), + GFP_KERNEL); + if (!phy->param_override_seq) { + ret = -ENOMEM; + goto err_ret; + } + + ret = of_property_read_u32_array(dev->of_node, + "qcom,param-override-seq", + phy->param_override_seq, + phy->param_override_seq_cnt); + if (ret) { + dev_err(dev, "qcom,param-override-seq read failed %d\n", + ret); + goto err_ret; + } + } + + if (phy->param_override_seq_cnt != 2 || + phy->param_override_seq[0] != 0 || phy->param_override_seq[1] != 0x58) + return dev_err_probe(dev, -EINVAL, "unexpected Uke PHY tuning sequence\n"); + + phy->ur = ur; + phy->phy.dev = dev; + platform_set_drvdata(pdev, phy); + + phy->phy.init = msm_eusb2_phy_init; + phy->phy.set_suspend = msm_eusb2_phy_set_suspend; + phy->phy.notify_connect = msm_eusb2_phy_notify_connect; + phy->phy.notify_disconnect = msm_eusb2_phy_notify_disconnect; + phy->phy.type = USB_PHY_TYPE_USB2; + + WRITE_ONCE(diag_probe, 8 * 10000 + EINPROGRESS); + ret = usb_add_phy_dev(&phy->phy); + if (ret) + goto err_ret; + + + /* + * EUD may be enable in boot loader and to keep EUD session alive across + * kernel boot till USB phy driver is initialized based on cable status, + * keep LDOs, clocks and repeater on here. + */ + if (is_eud_debug_mode_active(phy)) { + ret = msm_eusb2_phy_init(&phy->phy); + if (ret) { + usb_remove_phy(&phy->phy); + return ret; + } + } + + return 0; + +err_ret: + return ret; +} + +static void msm_eusb2_phy_remove(struct platform_device *pdev) +{ + struct msm_eusb2_phy *phy = platform_get_drvdata(pdev); + + usb_remove_phy(&phy->phy); + msm_eusb2_phy_clocks(phy, false); + if (msm_eusb2_phy_power(phy, false)) + return; + usb_repeater_reset(phy->ur, false); + usb_repeater_powerdown(phy->ur); +} + +static const struct of_device_id msm_usb_id_table[] = { + { + .compatible = "qcom,usb-snps-eusb2-phy", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, msm_usb_id_table); + +static struct platform_driver msm_eusb2_phy_driver = { + .probe = msm_eusb2_phy_probe, + .remove = msm_eusb2_phy_remove, + .driver = { + .name = "msm-eusb2-phy-uke", + .suppress_bind_attrs = true, + .of_match_table = of_match_ptr(msm_usb_id_table), + }, +}; + +module_platform_driver(msm_eusb2_phy_driver); +MODULE_DESCRIPTION("MSM USB eUSB2 PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/repeater/Kconfig b/drivers/usb/repeater/Kconfig new file mode 100644 index 000000000000..3cb9d034176b --- /dev/null +++ b/drivers/usb/repeater/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +config USB_REPEATER + bool "USB repeater framework" + depends on OF + +config USB_REPEATER_QCOM_UKE + tristate "Uke public Xiaomi PMIC eUSB2 repeater" + depends on OF && REGULATOR && (ARCH_QCOM || COMPILE_TEST) + select USB_REPEATER + select REGMAP diff --git a/drivers/usb/repeater/Makefile b/drivers/usb/repeater/Makefile new file mode 100644 index 000000000000..0e18066a364b --- /dev/null +++ b/drivers/usb/repeater/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_USB_REPEATER) += repeater.o +obj-$(CONFIG_USB_REPEATER_QCOM_UKE) += repeater-qti-pmic-eusb2-uke.o diff --git a/drivers/usb/repeater/repeater-qti-pmic-eusb2-uke.c b/drivers/usb/repeater/repeater-qti-pmic-eusb2-uke.c new file mode 100644 index 000000000000..5ec92c2d2d5f --- /dev/null +++ b/drivers/usb/repeater/repeater-qti-pmic-eusb2-uke.c @@ -0,0 +1,517 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021-2023, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#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 */ + +/* Repeater REVID registers */ +#define EUSB2_REVISION1 0x00 + +/* 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; + int power_error; + + 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 int eusb2_repeater_update_seq(struct eusb2_repeater *er, + u32 *seq, u8 cnt) +{ + int i, ret; + + 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]); + ret = eusb2_repeater_reg_write(er, seq[i+1], seq[i]); + if (ret) + return ret; + } + return 0; +} + +static int eusb2_repeater_get_version(struct usb_repeater *ur) +{ + struct eusb2_repeater *er = container_of(ur, struct eusb2_repeater, ur); + u8 reg; + int ret = eusb2_repeater_reg_read(er, ®, EUSB2_REVISION1, 1); + + return ret ? ret : reg; +} +static int eusb2_repeater_power_impl(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) + return ret; + +unset_vdd3: + ret = regulator_set_voltage(er->vdd3, 0, EUSB2_3P0_VOL_MAX); + if (ret < 0) + return ret; + +put_vdd3_lpm: + ret = regulator_set_load(er->vdd3, 0); + if (ret < 0) + return ret; + +disable_vdd18: + ret = regulator_disable(er->vdd18); + if (ret) + return ret; + +unset_vdd18: + ret = regulator_set_voltage(er->vdd18, 0, EUSB2_1P8_VOL_MAX); + if (ret < 0) + return ret; + +put_vdd18_lpm: + ret = regulator_set_load(er->vdd18, 0); + if (ret < 0) + return ret; + +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_power(struct eusb2_repeater *er, bool on) +{ + int ret; + + if (er->power_error) + return er->power_error; + ret = eusb2_repeater_power_impl(er, on); + if (ret) { + er->power_error = ret; + dev_err(er->ur.dev, + "power transaction failed: %d; further power changes blocked\n", ret); + } + return ret; +} + +static unsigned int diag_init; +module_param_named(diag_init, diag_init, uint, 0444); + +static int eusb2_repeater_init_impl(struct usb_repeater *ur); +static int eusb2_repeater_init(struct usb_repeater *ur) +{ + int ret; + + WRITE_ONCE(diag_init, 10000 + EINPROGRESS); + ret = eusb2_repeater_init_impl(ur); + WRITE_ONCE(diag_init, (READ_ONCE(diag_init) / 10000) * 10000 - ret); + return ret; +} + +static int eusb2_repeater_init_impl(struct usb_repeater *ur) +{ + struct eusb2_repeater *er = container_of(ur, struct eusb2_repeater, ur); + unsigned int rptr_init_cnt = INIT_MAX_CNT; + u8 status; + int ret; + + /* Uke experiment only supports the vendor device-mode sequence. */ + if (ur->flags & BIT(0)) + return -EOPNOTSUPP; + WRITE_ONCE(diag_init, 2 * 10000 + EINPROGRESS); + ret = eusb2_repeater_update_seq(er, er->param_override_seq, + er->param_override_seq_cnt); + if (ret) + return ret; + /* Clear retained host workaround exactly as in Xiaomi device mode. */ + WRITE_ONCE(diag_init, 3 * 10000 + EINPROGRESS); + ret = eusb2_repeater_masked_write(er, EUSB2_FORCE_EN_5, F_CLK_19P2M_EN, 0); + if (ret) + return ret; + ret = eusb2_repeater_masked_write(er, EUSB2_FORCE_VAL_5, V_CLK_19P2M_EN, 0); + if (ret) + return ret; + WRITE_ONCE(diag_init, 4 * 10000 + EINPROGRESS); + do { + ret = eusb2_repeater_reg_read(er, &status, EUSB2_RPTR_STATUS, 1); + if (ret) + return ret; + if (status & RPTR_OK) + return 0; + usleep_range(10, 20); + } while (--rptr_init_cnt); + return -ETIMEDOUT; +} + +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"); + return eusb2_repeater_masked_write(er, EUSB2_EN_CTL1, EUSB2_RPTR_EN, + bring_out_of_reset ? EUSB2_RPTR_EN : 0x0); +} + +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_read_overrides(struct device *dev, const char *prop, + u32 **seq, u8 *seq_cnt) +{ + int num_elem, ret; + + num_elem = of_property_count_elems_of_size(dev->of_node, prop, sizeof(**seq)); + if (num_elem > 0) { + if (num_elem > U8_MAX || num_elem % 2) { + dev_err(dev, "invalid len for %s\n", prop); + return -EINVAL; + } + + *seq_cnt = num_elem; + *seq = devm_kcalloc(dev, num_elem, sizeof(**seq), GFP_KERNEL); + if (!*seq) + return -ENOMEM; + + ret = of_property_read_u32_array(dev->of_node, prop, *seq, num_elem); + if (ret) { + dev_err(dev, "%s read failed %d\n", prop, ret); + return ret; + } + } + + return 0; +} + +static unsigned int diag_probe; +module_param_named(diag_probe, diag_probe, uint, 0444); + +static int eusb2_repeater_probe_impl(struct platform_device *pdev); +static int eusb2_repeater_probe(struct platform_device *pdev) +{ + int ret; + + WRITE_ONCE(diag_probe, 10000 + EINPROGRESS); + ret = eusb2_repeater_probe_impl(pdev); + WRITE_ONCE(diag_probe, (READ_ONCE(diag_probe) / 10000) * 10000 - ret); + return ret; +} + +static int eusb2_repeater_probe_impl(struct platform_device *pdev) +{ + struct eusb2_repeater *er; + struct device *dev = &pdev->dev; + + int ret = 0; + u32 base; + + er = devm_kzalloc(dev, sizeof(*er), GFP_KERNEL); + if (!er) { + ret = -ENOMEM; + goto err_probe; + } + + WRITE_ONCE(diag_probe, 2 * 10000 + EINPROGRESS); + 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; + WRITE_ONCE(diag_probe, 3 * 10000 + EINPROGRESS); + 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; + } + + WRITE_ONCE(diag_probe, 4 * 10000 + EINPROGRESS); + 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; + } + /*This is the device parameters.*/ + WRITE_ONCE(diag_probe, 5 * 10000 + EINPROGRESS); + ret = eusb2_repeater_read_overrides(dev, "qcom,param-override-seq", + &er->param_override_seq, &er->param_override_seq_cnt); + if (ret < 0) + 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; + er->ur.get_version = eusb2_repeater_get_version; + + WRITE_ONCE(diag_probe, 6 * 10000 + EINPROGRESS); + ret = usb_add_repeater_dev(&er->ur); + if (ret) + goto err_probe; + + return 0; + +err_probe: + return ret; +} + +static void eusb2_repeater_remove(struct platform_device *pdev) +{ + struct eusb2_repeater *er = platform_get_drvdata(pdev); + + if (!er) + return; + + usb_remove_repeater_dev(&er->ur); + eusb2_repeater_power(er, false); +} + +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-uke", + .suppress_bind_attrs = true, + .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..5c1263da718c --- /dev/null +++ b/drivers/usb/repeater/repeater.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021-2023, 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) { + struct device *dev = r->dev; + + if (dev->driver) + module_put(dev->driver->owner); + put_device(dev); + } +} +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(*(struct usb_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; + } + + *r_devm = r; + 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); + +struct usb_repeater *usb_get_repeater_by_node(struct device_node *node) +{ + struct usb_repeater *r = ERR_PTR(-ENODEV); + unsigned long flags; + + spin_lock_irqsave(&repeater_lock, flags); + r = of_usb_find_repeater(node); + if (!IS_ERR(r)) { + if (!try_module_get(r->dev->driver->owner)) + r = ERR_PTR(-ENODEV); + else + get_device(r->dev); + } + spin_unlock_irqrestore(&repeater_lock, flags); + + return r; +} +EXPORT_SYMBOL(usb_get_repeater_by_node); + +struct usb_repeater *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 = usb_get_repeater_by_node(node); + of_node_put(node); + return r; +} +EXPORT_SYMBOL(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..284b1ae5121a --- /dev/null +++ b/include/linux/usb/repeater.h @@ -0,0 +1,128 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021-2023, Qualcomm Innovation Center, Inc. All rights reserved. + */ +#ifndef __LINUX_USB_REPEATER_H +#define __LINUX_USB_REPEATER_H + +#include +#include +#include + +#if IS_ENABLED(CONFIG_MI_NXP_EUSB2_REPEATER) +#define UR_AUTO_RESUME_SUPPORTED BIT(0) +#endif +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); +#if IS_ENABLED(CONFIG_MI_NXP_EUSB2_REPEATER) + int (*init_nxp)(struct usb_repeater *x, unsigned int flags); +#endif + 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); + int (*get_version)(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); +struct usb_repeater *usb_get_repeater_by_phandle(struct device *dev, + const char *phandle, u8 index); +struct usb_repeater *usb_get_repeater_by_node(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 struct usb_repeater *usb_get_repeater_by_phandle( + struct device *d, const char *phandle, u8 index) +{ return ERR_PTR(-ENXIO); } + +static inline struct usb_repeater *usb_get_repeater_by_node( + 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; +} + +#if IS_ENABLED(CONFIG_MI_NXP_EUSB2_REPEATER) +static inline int usb_repeater_init(struct usb_repeater *r, unsigned int flags) +{ + if (r && r->init_nxp != NULL) + return r->init_nxp(r, flags); + else + return 0; +} +#else +static inline int usb_repeater_init(struct usb_repeater *r) +{ + if (r && r->init != NULL) + return r->init(r); + else + return 0; +} +#endif + +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; +} + +static inline int usb_repeater_get_version(struct usb_repeater *r) +{ + if (r && r->get_version != NULL) + return r->get_version(r); + else + return -EINVAL; +} +#endif /* __LINUX_USB_REPEATER_H */