From 75a57275dd2b338a72a647a6732f0487d64ac789 Mon Sep 17 00:00:00 2001 From: Linyu Yuan Date: Thu, 10 Nov 2022 10:35:07 +0800 Subject: [PATCH] usb: dwc3: msm: support usb3 linear redriver redriver framework snapshot from msm-5.15 commit 32dbffee2bb3 ("usb: redriver: add nb7vpq904m rework driver") commit 4465e83b503c ("usb: redriver: add core api"). Change-Id: Ie9b84641fc97f9e45bfd7f7eeeee1eba4bfd64f1 Signed-off-by: Linyu Yuan --- drivers/usb/Kconfig | 2 + drivers/usb/Makefile | 2 + drivers/usb/dwc3/dwc3-msm-core.c | 18 +- drivers/usb/redriver/Kconfig | 24 + drivers/usb/redriver/Makefile | 4 + drivers/usb/redriver/nb7vpq904m.c | 1136 +++++++++++++++++++++++++++++ drivers/usb/redriver/redriver.c | 204 ++++++ include/linux/usb/redriver.h | 12 +- 8 files changed, 1394 insertions(+), 8 deletions(-) create mode 100644 drivers/usb/redriver/Kconfig create mode 100644 drivers/usb/redriver/Makefile create mode 100644 drivers/usb/redriver/nb7vpq904m.c create mode 100644 drivers/usb/redriver/redriver.c diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index d25b2ece65d3..db319c88acd4 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -176,4 +176,6 @@ source "drivers/usb/roles/Kconfig" source "drivers/usb/repeater/Kconfig" +source "drivers/usb/redriver/Kconfig" + endif # USB_SUPPORT diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index dabeed7812fd..f61c3087df19 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -68,3 +68,5 @@ obj-$(CONFIG_TYPEC) += typec/ obj-$(CONFIG_USB_ROLE_SWITCH) += roles/ obj-$(CONFIG_USB_REPEATER) += repeater/ + +obj-$(CONFIG_USB_REDRIVER) += redriver/ diff --git a/drivers/usb/dwc3/dwc3-msm-core.c b/drivers/usb/dwc3/dwc3-msm-core.c index d4d225019b95..d26900bb9873 100644 --- a/drivers/usb/dwc3/dwc3-msm-core.c +++ b/drivers/usb/dwc3/dwc3-msm-core.c @@ -5241,11 +5241,14 @@ static void dwc3_msm_clear_dp_only_params(struct dwc3_msm *mdwc) mdwc->ss_release_called = false; mdwc->ss_phy->flags &= ~PHY_DP_MODE; dwc3_msm_set_max_speed(mdwc, USB_SPEED_UNKNOWN); + + usb_redriver_notify_disconnect(mdwc->redriver); } static void dwc3_msm_set_dp_only_params(struct dwc3_msm *mdwc) { - usb_redriver_release_lanes(mdwc->redriver, 4); + usb_redriver_release_lanes(mdwc->redriver, mdwc->ss_phy->flags & PHY_LANE_A ? + ORIENTATION_CC1 : ORIENTATION_CC2, 4); /* restart USB host mode into high speed */ mdwc->ss_release_called = true; @@ -5306,7 +5309,8 @@ int dwc3_msm_set_dp_mode(struct device *dev, bool dp_connected, int lanes) mdwc->dp_state = DP_2_LANE; mdwc->refcnt_dp_usb++; mutex_unlock(&mdwc->role_switch_mutex); - usb_redriver_release_lanes(mdwc->redriver, 2); + usb_redriver_release_lanes(mdwc->redriver, mdwc->ss_phy->flags & PHY_LANE_A ? + ORIENTATION_CC1 : ORIENTATION_CC2, 2); pm_runtime_get_sync(&mdwc->dwc3->dev); mdwc->ss_phy->flags |= PHY_USB_DP_CONCURRENT_MODE; pm_runtime_put_sync(&mdwc->dwc3->dev); @@ -6200,6 +6204,10 @@ static int dwc3_otg_start_host(struct dwc3_msm *mdwc, int on) dbg_event(0xFF, "StrtHost gync", atomic_read(&mdwc->dev->power.usage_count)); clk_set_rate(mdwc->core_clk, mdwc->core_clk_rate); + + usb_redriver_notify_connect(mdwc->redriver, + mdwc->ss_phy->flags & PHY_LANE_A ? + ORIENTATION_CC1 : ORIENTATION_CC2); if (dwc3_msm_get_max_speed(mdwc) >= USB_SPEED_SUPER) { mdwc->ss_phy->flags |= PHY_HOST_MODE; usb_phy_notify_connect(mdwc->ss_phy, @@ -6313,6 +6321,7 @@ static int dwc3_otg_start_host(struct dwc3_msm *mdwc, int on) USB_SPEED_SUPER); mdwc->ss_phy->flags &= ~PHY_HOST_MODE; } + usb_redriver_notify_disconnect(mdwc->redriver); mdwc->hs_phy->flags &= ~PHY_HOST_MODE; usb_unregister_notify(&mdwc->host_nb); @@ -6388,6 +6397,10 @@ static int dwc3_otg_start_peripheral(struct dwc3_msm *mdwc, int on) dwc3_msm_notify_event(dwc, DWC3_GSI_EVT_BUF_SETUP, 0); dwc3_override_vbus_status(mdwc, true); + + usb_redriver_notify_connect(mdwc->redriver, + mdwc->ss_phy->flags & PHY_LANE_A ? + ORIENTATION_CC1 : ORIENTATION_CC2); if (dwc3_msm_get_max_speed(mdwc) >= USB_SPEED_SUPER) usb_phy_notify_connect(mdwc->ss_phy, USB_SPEED_SUPER); @@ -6434,6 +6447,7 @@ static int dwc3_otg_start_peripheral(struct dwc3_msm *mdwc, int on) mdwc->in_device_mode = false; usb_phy_notify_disconnect(mdwc->hs_phy, USB_SPEED_HIGH); usb_phy_notify_disconnect(mdwc->ss_phy, USB_SPEED_SUPER); + usb_redriver_notify_disconnect(mdwc->redriver); dwc3_msm_notify_event(dwc, DWC3_GSI_EVT_BUF_CLEAR, 0); dwc3_override_vbus_status(mdwc, false); diff --git a/drivers/usb/redriver/Kconfig b/drivers/usb/redriver/Kconfig new file mode 100644 index 000000000000..783243931774 --- /dev/null +++ b/drivers/usb/redriver/Kconfig @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0-only + +menu "USB3 linear redriver" + +config USB_REDRIVER + tristate "USB super speed (plus) redriver support" + help + USB super speed (plus) redriver chip used to improve + USB signal which have long path(USB connector <--> + FPC cable <---> USB PHY). + + Normally this kind of chip have i2c bus interface. + +config USB_REDRIVER_NB7VPQ904M + tristate "NB7VPQ904M USB 3.1 Gen1/Gen2 10Gbps redriver" + select REGMAP_I2C + select USB_REDRIVER + help + NB7VPQ904M come from ON-SEMI company, + it enhance USB super (plus) signal on mobile platform. + it have i2c program interface. + Say Y/M here if you want to support it. + +endmenu diff --git a/drivers/usb/redriver/Makefile b/drivers/usb/redriver/Makefile new file mode 100644 index 000000000000..32f42cbc4c8b --- /dev/null +++ b/drivers/usb/redriver/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_USB_REDRIVER) += redriver.o +obj-$(CONFIG_USB_REDRIVER_NB7VPQ904M) += nb7vpq904m.o diff --git a/drivers/usb/redriver/nb7vpq904m.c b/drivers/usb/redriver/nb7vpq904m.c new file mode 100644 index 000000000000..da592d983f90 --- /dev/null +++ b/drivers/usb/redriver/nb7vpq904m.c @@ -0,0 +1,1136 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* priority: INT_MAX >= x >= 0 */ +#define NOTIFIER_PRIORITY 1 + +/* Registers Address */ +#define GEN_DEV_SET_REG 0x00 +#define CHIP_VERSION_REG 0x17 + +#define REDRIVER_REG_MAX 0x1f + +#define EQ_SET_REG_BASE 0x01 +#define FLAT_GAIN_REG_BASE 0x18 +#define OUT_COMP_AND_POL_REG_BASE 0x02 +#define LOSS_MATCH_REG_BASE 0x19 + +#define AUX_SWITCH_REG 0x09 +#define AUX_NORMAL_VAL 0 +#define AUX_FLIP_VAL 1 +#define AUX_DISABLE_VAL 2 + +/* Default Register Value */ +#define GEN_DEV_SET_REG_DEFAULT 0xFB + +/* Register bits */ +/* General Device Settings Register Bits */ +#define CHIP_EN BIT(0) +#define CHNA_EN BIT(4) +#define CHNB_EN BIT(5) +#define CHNC_EN BIT(6) +#define CHND_EN BIT(7) + +#define CHANNEL_NUM 4 + +#define OP_MODE_SHIFT 1 + +#define EQ_SETTING_MASK 0x07 +#define OUTPUT_COMPRESSION_MASK 0x0b +#define LOSS_MATCH_MASK 0x03 +#define FLAT_GAIN_MASK 0x03 + +#define EQ_SETTING_SHIFT 0x01 +#define OUTPUT_COMPRESSION_SHIFT 0x01 +#define LOSS_MATCH_SHIFT 0x00 +#define FLAT_GAIN_SHIFT 0x00 + +#define CHNA_INDEX 0 +#define CHNB_INDEX 1 +#define CHNC_INDEX 2 +#define CHND_INDEX 3 + +enum operation_mode { + OP_MODE_NONE, /* 4 lanes disabled */ + OP_MODE_USB, /* 2 lanes for USB and 2 lanes disabled */ + OP_MODE_DP, /* 4 lanes DP */ + OP_MODE_USB_AND_DP, /* 2 lanes for USB and 2 lanes DP */ + OP_MODE_DEFAULT, /* 4 lanes USB */ +}; + +#define CHAN_MODE_USB 0 +#define CHAN_MODE_DP 1 +#define CHAN_MODE_NUM 2 + +#define CHAN_MODE_DISABLE 0xff /* when disable, not configure eq, gain ... */ + +#define LANES_DP 4 +#define LANES_DP_AND_USB 2 + +#define PULLUP_WORKER_DELAY_US 500000 + +#define CHIP_MAX_PWR_UA 260000 +#define CHIP_MIN_PWR_UV 1710000 +#define CHIP_MAX_PWR_UV 1890000 + +struct nb7vpq904m_redriver { + struct usb_redriver r; + struct device *dev; + struct regmap *regmap; + struct i2c_client *client; + struct regulator *vdd; + + int orientation_gpio; + int typec_orientation; + enum operation_mode op_mode; + + u8 chan_mode[CHANNEL_NUM]; + + u8 eq[CHAN_MODE_NUM][CHANNEL_NUM]; + u8 output_comp[CHAN_MODE_NUM][CHANNEL_NUM]; + u8 loss_match[CHAN_MODE_NUM][CHANNEL_NUM]; + u8 flat_gain[CHAN_MODE_NUM][CHANNEL_NUM]; + + u8 gen_dev_val; + bool lane_channel_swap; + bool vdd_enable; + bool is_set_aux; + + struct workqueue_struct *pullup_wq; + struct work_struct pullup_work; + bool work_ongoing; + + struct work_struct host_work; + + struct dentry *debug_root; +}; + +static int nb7vpq904m_channel_update(struct nb7vpq904m_redriver *redriver); +static void nb7vpq904m_debugfs_entries(struct nb7vpq904m_redriver *redriver); + +static const char * const opmode_string[] = { + [OP_MODE_NONE] = "NONE", + [OP_MODE_USB] = "USB", + [OP_MODE_DP] = "DP", + [OP_MODE_USB_AND_DP] = "USB and DP", + [OP_MODE_DEFAULT] = "DEFAULT", +}; +#define OPMODESTR(x) opmode_string[x] + +static int nb7vpq904m_reg_set(struct nb7vpq904m_redriver *redriver, + u8 reg, u8 val) +{ + int ret; + + ret = regmap_write(redriver->regmap, (unsigned int)reg, + (unsigned int)val); + if (ret < 0) { + dev_err(redriver->dev, "writing reg 0x%02x failure\n", reg); + return ret; + } + + dev_dbg(redriver->dev, "writing reg 0x%02x=0x%02x\n", reg, val); + + return 0; +} + +static void nb7vpq904m_vdd_enable(struct nb7vpq904m_redriver *redriver, bool on) +{ + int l, v, s; + + if (!redriver->vdd || redriver->op_mode != OP_MODE_NONE) { + dev_dbg(redriver->dev, "no vdd regulator operation\n"); + return; + } + + if (on && !redriver->vdd_enable) { + redriver->vdd_enable = true; + l = regulator_set_load(redriver->vdd, CHIP_MAX_PWR_UA); + v = regulator_set_voltage(redriver->vdd, CHIP_MIN_PWR_UV, CHIP_MAX_PWR_UV); + s = regulator_enable(redriver->vdd); + dev_dbg(redriver->dev, "vdd regulator enable return %d-%d-%d\n", l, v, s); + } else if (!on && redriver->vdd_enable) { + redriver->vdd_enable = false; + s = regulator_disable(redriver->vdd); + v = regulator_set_voltage(redriver->vdd, 0, CHIP_MAX_PWR_UV); + l = regulator_set_load(redriver->vdd, 0); + dev_dbg(redriver->dev, "vdd regulator disable return %d-%d-%d\n", l, v, s); + } +} + +static void nb7vpq904m_dev_aux_set(struct nb7vpq904m_redriver *redriver) +{ + u8 aux_val = AUX_DISABLE_VAL; + + if (!redriver->is_set_aux) + return; + + switch (redriver->op_mode) { + case OP_MODE_DP: + case OP_MODE_USB_AND_DP: + if (redriver->typec_orientation == ORIENTATION_CC1) + aux_val = AUX_NORMAL_VAL; + else + aux_val = AUX_FLIP_VAL; + break; + default: + break; + } + + nb7vpq904m_reg_set(redriver, AUX_SWITCH_REG, aux_val); +} + +static int nb7vpq904m_gen_dev_set(struct nb7vpq904m_redriver *redriver) +{ + u8 val = 0; + + switch (redriver->op_mode) { + case OP_MODE_DEFAULT: + /* Enable channel A, B, C and D */ + val |= (CHNA_EN | CHNB_EN); + val |= (CHNC_EN | CHND_EN); + val |= (0x5 << OP_MODE_SHIFT); + val |= CHIP_EN; + break; + case OP_MODE_USB: + /* Use source side I/O mapping */ + if (redriver->typec_orientation + == ORIENTATION_CC1) { + /* Enable channel C and D */ + val &= ~(CHNA_EN | CHNB_EN); + val |= (CHNC_EN | CHND_EN); + } else if (redriver->typec_orientation + == ORIENTATION_CC2) { + /* Enable channel A and B*/ + val |= (CHNA_EN | CHNB_EN); + val &= ~(CHNC_EN | CHND_EN); + } + + /* Set to default USB Mode */ + val |= (0x5 << OP_MODE_SHIFT); + val |= CHIP_EN; + break; + case OP_MODE_DP: + /* Enable channel A, B, C and D */ + val |= (CHNA_EN | CHNB_EN); + val |= (CHNC_EN | CHND_EN); + + /* Set to DP 4 Lane Mode (OP Mode 2) */ + val |= (0x2 << OP_MODE_SHIFT); + val |= CHIP_EN; + break; + case OP_MODE_USB_AND_DP: + /* Enable channel A, B, C and D */ + val |= (CHNA_EN | CHNB_EN); + val |= (CHNC_EN | CHND_EN); + val |= CHIP_EN; + + if (redriver->typec_orientation + == ORIENTATION_CC1) + val |= (0x1 << OP_MODE_SHIFT); + else if (redriver->typec_orientation + == ORIENTATION_CC2) + val |= (0x0 << OP_MODE_SHIFT); + + break; + default: + val &= ~CHIP_EN; + break; + } + + redriver->gen_dev_val = val; + + return nb7vpq904m_reg_set(redriver, GEN_DEV_SET_REG, val); +} + +static int nb7vpq904m_param_config(struct nb7vpq904m_redriver *redriver, + u8 reg_base, u8 channel, u8 chan_mode, u8 mask, u8 shift, + u8 val, u8 (*stored_val)[CHANNEL_NUM]) +{ + int i, j, ret = -EINVAL; + u8 reg_addr, reg_val; + + if (channel == CHANNEL_NUM) { + for (i = 0; i < CHAN_MODE_NUM; i++) + for (j = 0; j < CHANNEL_NUM; j++) { + if (redriver->chan_mode[j] == i) { + reg_addr = reg_base + (j << 1); + + reg_val = (val << shift); + reg_val &= (mask << shift); + + ret = nb7vpq904m_reg_set(redriver, + reg_addr, reg_val); + if (ret < 0) + return ret; + } + + stored_val[i][j] = val; + } + } else { + if (redriver->chan_mode[channel] == chan_mode) { + reg_addr = reg_base + (channel << 1); + + reg_val = (val << shift); + reg_val &= (mask << shift); + + ret = nb7vpq904m_reg_set(redriver, + reg_addr, reg_val); + if (ret < 0) + return ret; + } + + stored_val[chan_mode][channel] = val; + } + + return 0; +} + +static int nb7vpq904m_eq_config( + struct nb7vpq904m_redriver *redriver, u8 channel, u8 chan_mode, u8 val) +{ + return nb7vpq904m_param_config(redriver, + EQ_SET_REG_BASE, channel, chan_mode, + EQ_SETTING_MASK, EQ_SETTING_SHIFT, + val, redriver->eq); +} + +static int nb7vpq904m_flat_gain_config( + struct nb7vpq904m_redriver *redriver, u8 channel, u8 chan_mode, u8 val) +{ + return nb7vpq904m_param_config(redriver, + FLAT_GAIN_REG_BASE, channel, chan_mode, + FLAT_GAIN_MASK, FLAT_GAIN_SHIFT, + val, redriver->flat_gain); +} + +static int nb7vpq904m_output_comp_config( + struct nb7vpq904m_redriver *redriver, u8 channel, u8 chan_mode, u8 val) +{ + return nb7vpq904m_param_config(redriver, + OUT_COMP_AND_POL_REG_BASE, channel, chan_mode, + OUTPUT_COMPRESSION_MASK, OUTPUT_COMPRESSION_SHIFT, + val, redriver->output_comp); +} + +static int nb7vpq904m_loss_match_config( + struct nb7vpq904m_redriver *redriver, u8 channel, u8 chan_mode, u8 val) +{ + return nb7vpq904m_param_config(redriver, + LOSS_MATCH_REG_BASE, channel, chan_mode, + LOSS_MATCH_MASK, LOSS_MATCH_SHIFT, val, + redriver->loss_match); +} + +static int nb7vpq904m_channel_update(struct nb7vpq904m_redriver *redriver) +{ + int ret; + u8 i, chan_mode; + + switch (redriver->op_mode) { + case OP_MODE_DEFAULT: + redriver->chan_mode[CHNA_INDEX] = CHAN_MODE_USB; + redriver->chan_mode[CHNB_INDEX] = CHAN_MODE_USB; + redriver->chan_mode[CHNC_INDEX] = CHAN_MODE_USB; + redriver->chan_mode[CHND_INDEX] = CHAN_MODE_USB; + break; + case OP_MODE_USB: + if (redriver->typec_orientation == ORIENTATION_CC1) { + redriver->chan_mode[CHNA_INDEX] = CHAN_MODE_DISABLE; + redriver->chan_mode[CHNB_INDEX] = CHAN_MODE_DISABLE; + redriver->chan_mode[CHNC_INDEX] = CHAN_MODE_USB; + redriver->chan_mode[CHND_INDEX] = CHAN_MODE_USB; + } else { + redriver->chan_mode[CHNA_INDEX] = CHAN_MODE_USB; + redriver->chan_mode[CHNB_INDEX] = CHAN_MODE_USB; + redriver->chan_mode[CHNC_INDEX] = CHAN_MODE_DISABLE; + redriver->chan_mode[CHND_INDEX] = CHAN_MODE_DISABLE; + } + break; + case OP_MODE_USB_AND_DP: + if (redriver->typec_orientation == ORIENTATION_CC1) { + redriver->chan_mode[CHNA_INDEX] = CHAN_MODE_DP; + redriver->chan_mode[CHNB_INDEX] = CHAN_MODE_DP; + redriver->chan_mode[CHNC_INDEX] = CHAN_MODE_USB; + redriver->chan_mode[CHND_INDEX] = CHAN_MODE_USB; + } else { + redriver->chan_mode[CHNA_INDEX] = CHAN_MODE_USB; + redriver->chan_mode[CHNB_INDEX] = CHAN_MODE_USB; + redriver->chan_mode[CHNC_INDEX] = CHAN_MODE_DP; + redriver->chan_mode[CHND_INDEX] = CHAN_MODE_DP; + } + break; + case OP_MODE_DP: + redriver->chan_mode[CHNA_INDEX] = CHAN_MODE_DP; + redriver->chan_mode[CHNB_INDEX] = CHAN_MODE_DP; + redriver->chan_mode[CHNC_INDEX] = CHAN_MODE_DP; + redriver->chan_mode[CHND_INDEX] = CHAN_MODE_DP; + break; + default: + return 0; + } + + for (i = 0; i < CHANNEL_NUM; i++) { + if (redriver->chan_mode[i] == CHAN_MODE_DISABLE) + continue; + + chan_mode = redriver->chan_mode[i]; + + ret = nb7vpq904m_eq_config(redriver, i, chan_mode, + redriver->eq[chan_mode][i]); + if (ret) + goto err; + + ret = nb7vpq904m_flat_gain_config(redriver, i, chan_mode, + redriver->flat_gain[chan_mode][i]); + if (ret) + goto err; + + ret = nb7vpq904m_output_comp_config(redriver, i, chan_mode, + redriver->output_comp[chan_mode][i]); + if (ret) + goto err; + + ret = nb7vpq904m_loss_match_config(redriver, i, chan_mode, + redriver->loss_match[chan_mode][i]); + if (ret) + goto err; + } + + return 0; + +err: + dev_err(redriver->dev, "channel parameters update failure(%d).\n", ret); + return ret; +} + +static int nb7vpq904m_read_configuration(struct nb7vpq904m_redriver *redriver) +{ + struct device_node *node = redriver->dev->of_node; + int ret = 0; + + if (of_find_property(node, "eq", NULL)) { + ret = of_property_read_u8_array(node, "eq", + redriver->eq[0], sizeof(redriver->eq)); + if (ret) + goto err; + } + + if (of_find_property(node, "flat-gain", NULL)) { + ret = of_property_read_u8_array(node, + "flat-gain", redriver->flat_gain[0], + sizeof(redriver->flat_gain)); + if (ret) + goto err; + } + + if (of_find_property(node, "output-comp", NULL)) { + ret = of_property_read_u8_array(node, + "output-comp", redriver->output_comp[0], + sizeof(redriver->output_comp)); + if (ret) + goto err; + } + + if (of_find_property(node, "loss-match", NULL)) { + ret = of_property_read_u8_array(node, + "loss-match", redriver->loss_match[0], + sizeof(redriver->loss_match)); + if (ret) + goto err; + } + + redriver->is_set_aux = of_property_read_bool(node, "set-aux"); + + return 0; + +err: + dev_err(redriver->dev, + "%s: error read parameters.\n", __func__); + return ret; +} + +static int nb7vpq904m_get_orientation(struct usb_redriver *r) +{ + struct nb7vpq904m_redriver *redriver = + container_of(r, struct nb7vpq904m_redriver, r); + + dev_dbg(redriver->dev, "%s: mode %s\n", __func__, + OPMODESTR(redriver->op_mode)); + + return gpio_get_value(redriver->orientation_gpio); +} + +static inline void orientation_set(struct nb7vpq904m_redriver *redriver, int ort) +{ + redriver->typec_orientation = ort; + + if (redriver->lane_channel_swap) { + if (redriver->typec_orientation == ORIENTATION_CC1) + redriver->typec_orientation = ORIENTATION_CC2; + else + redriver->typec_orientation = ORIENTATION_CC1; + } +} + +static int nb7vpq904m_notify_connect(struct usb_redriver *r, int ort) +{ + struct nb7vpq904m_redriver *redriver = + container_of(r, struct nb7vpq904m_redriver, r); + + dev_dbg(redriver->dev, "%s: mode %s, orientation %s, %d\n", __func__, + OPMODESTR(redriver->op_mode), + ort == ORIENTATION_CC1 ? "CC1" : "CC2", + redriver->lane_channel_swap); + + nb7vpq904m_vdd_enable(redriver, true); + + if (redriver->op_mode == OP_MODE_NONE) + redriver->op_mode = OP_MODE_USB; + + orientation_set(redriver, ort); + + nb7vpq904m_gen_dev_set(redriver); + nb7vpq904m_channel_update(redriver); + + return 0; +} + +static int nb7vpq904m_notify_disconnect(struct usb_redriver *r) +{ + struct nb7vpq904m_redriver *redriver = + container_of(r, struct nb7vpq904m_redriver, r); + + dev_dbg(redriver->dev, "%s: mode %s\n", __func__, + OPMODESTR(redriver->op_mode)); + + if (redriver->op_mode == OP_MODE_NONE) + return 0; + + redriver->op_mode = OP_MODE_NONE; + nb7vpq904m_reg_set(redriver, GEN_DEV_SET_REG, 0); + + nb7vpq904m_vdd_enable(redriver, false); + + return 0; +} + +static int nb7vpq904m_release_usb_lanes(struct usb_redriver *r, int ort, int num) +{ + struct nb7vpq904m_redriver *redriver = + container_of(r, struct nb7vpq904m_redriver, r); + + dev_dbg(redriver->dev, "%s: mode %s, orientation %s-%d, lanes %d\n", __func__, + OPMODESTR(redriver->op_mode), ort == ORIENTATION_CC1 ? "CC1" : "CC2", + redriver->lane_channel_swap, num); + + if (num == LANES_DP) + redriver->op_mode = OP_MODE_DP; + else if (num == LANES_DP_AND_USB) + redriver->op_mode = OP_MODE_USB_AND_DP; + + /* in case it need aux function from redriver and the first call is release lane */ + orientation_set(redriver, ort); + + nb7vpq904m_gen_dev_set(redriver); + + nb7vpq904m_dev_aux_set(redriver); + + nb7vpq904m_channel_update(redriver); + + return 0; +} + +static void nb7vpq904m_gadget_pullup_work(struct work_struct *w) +{ + struct nb7vpq904m_redriver *redriver = + container_of(w, struct nb7vpq904m_redriver, pullup_work); + u8 val = redriver->gen_dev_val; + + nb7vpq904m_reg_set(redriver, GEN_DEV_SET_REG, val & ~CHIP_EN); + usleep_range(1000, 1500); + nb7vpq904m_reg_set(redriver, GEN_DEV_SET_REG, val); + + redriver->work_ongoing = false; +} + +static int nb7vpq904m_gadget_pullup_enter(struct usb_redriver *r, int is_on) +{ + struct nb7vpq904m_redriver *redriver = + container_of(r, struct nb7vpq904m_redriver, r); + u64 time = 0; + + dev_dbg(redriver->dev, "%s: mode %s, %d, %d\n", __func__, + OPMODESTR(redriver->op_mode), is_on, redriver->work_ongoing); + + if (redriver->op_mode != OP_MODE_USB) + return -EINVAL; + + if (!is_on) + return 0; + + while (redriver->work_ongoing) { + /* + * this function can work in atomic context, no sleep function here, + * it need wait pull down complete before pull up again. + */ + udelay(1); + if (time++ > PULLUP_WORKER_DELAY_US) { + dev_warn(redriver->dev, "pullup timeout\n"); + break; + } + } + + dev_dbg(redriver->dev, "pull-up disable work took %llu us\n", time); + + return 0; +} + +static int nb7vpq904m_gadget_pullup_exit(struct usb_redriver *r, int is_on) +{ + struct nb7vpq904m_redriver *redriver = + container_of(r, struct nb7vpq904m_redriver, r); + + dev_dbg(redriver->dev, "%s: mode %s, %d, %d\n", __func__, + OPMODESTR(redriver->op_mode), is_on, redriver->work_ongoing); + + if (redriver->op_mode != OP_MODE_USB) + return -EINVAL; + + if (is_on) + return 0; + + redriver->work_ongoing = true; + queue_work(redriver->pullup_wq, &redriver->pullup_work); + + return 0; +} + +static void nb7vpq904m_host_work(struct work_struct *w) +{ + struct nb7vpq904m_redriver *redriver = + container_of(w, struct nb7vpq904m_redriver, host_work); + u8 val = redriver->gen_dev_val; + + nb7vpq904m_reg_set(redriver, GEN_DEV_SET_REG, val & ~CHIP_EN); + /* sleep for a while to make sure xhci host detect device disconnect */ + usleep_range(2000, 2500); + nb7vpq904m_reg_set(redriver, GEN_DEV_SET_REG, val); +} + +static int nb7vpq904m_host_powercycle(struct usb_redriver *r) +{ + struct nb7vpq904m_redriver *redriver = + container_of(r, struct nb7vpq904m_redriver, r); + + if (redriver->op_mode != OP_MODE_USB) + return -EINVAL; + + schedule_work(&redriver->host_work); + + return 0; +} + +static void nb7vpq904m_orientation_gpio_init( + struct nb7vpq904m_redriver *redriver) +{ + struct device *dev = redriver->dev; + int rc; + + redriver->orientation_gpio = of_get_gpio(dev->of_node, 0); + if (!gpio_is_valid(redriver->orientation_gpio)) { + dev_err(dev, "Failed to get gpio\n"); + return; + } + + rc = devm_gpio_request_one(dev, redriver->orientation_gpio, GPIOF_IN, "redriver"); + if (rc < 0) { + dev_err(dev, "Failed to request gpio\n"); + redriver->orientation_gpio = -EINVAL; + return; + } + + redriver->r.has_orientation = true; +} + +static const struct regmap_config redriver_regmap = { + .max_register = REDRIVER_REG_MAX, + .reg_bits = 8, + .val_bits = 8, +}; + +static int nb7vpq904m_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + struct nb7vpq904m_redriver *redriver; + int ret; + + redriver = devm_kzalloc(&client->dev, sizeof(struct nb7vpq904m_redriver), + GFP_KERNEL); + if (!redriver) + return -ENOMEM; + + redriver->pullup_wq = alloc_workqueue("%s:pullup", + WQ_UNBOUND | WQ_HIGHPRI, 0, + dev_name(&client->dev)); + if (!redriver->pullup_wq) { + dev_err(&client->dev, "Failed to create pullup workqueue\n"); + return -ENOMEM; + } + + redriver->regmap = devm_regmap_init_i2c(client, &redriver_regmap); + if (IS_ERR(redriver->regmap)) { + ret = PTR_ERR(redriver->regmap); + dev_err(&client->dev, + "Failed to allocate register map: %d\n", ret); + return ret; + } + + redriver->dev = &client->dev; + i2c_set_clientdata(client, redriver); + + ret = nb7vpq904m_read_configuration(redriver); + if (ret < 0) { + dev_err(&client->dev, + "Failed to read default configuration: %d\n", ret); + return ret; + } + + redriver->vdd = devm_regulator_get_optional(&client->dev, "vdd"); + if (IS_ERR(redriver->vdd)) { + ret = PTR_ERR(redriver->vdd); + redriver->vdd = NULL; + if (ret != -ENODEV) + dev_err(&client->dev, "Failed to get vdd regulator %d\n", ret); + } + + INIT_WORK(&redriver->pullup_work, nb7vpq904m_gadget_pullup_work); + INIT_WORK(&redriver->host_work, nb7vpq904m_host_work); + + redriver->lane_channel_swap = + of_property_read_bool(redriver->dev->of_node, "lane-channel-swap"); + + /* disable it at start, one i2c register write time is acceptable */ + redriver->op_mode = OP_MODE_NONE; + nb7vpq904m_vdd_enable(redriver, true); + nb7vpq904m_gen_dev_set(redriver); + /* when private vdd present and change to none mode, it can simply disable vdd regulator, + * but to keep things simple and avoid if/else operation, keep one same rule as, + * allow original register write operation then control vdd regulator. + * also it will keep consistent behavior if it still need vdd control when multiple + * clients share the same vdd regulator. + */ + nb7vpq904m_vdd_enable(redriver, false); + + nb7vpq904m_orientation_gpio_init(redriver); + + nb7vpq904m_debugfs_entries(redriver); + + redriver->r.of_node = redriver->dev->of_node; + redriver->r.release_usb_lanes = nb7vpq904m_release_usb_lanes; + redriver->r.notify_connect = nb7vpq904m_notify_connect; + redriver->r.notify_disconnect = nb7vpq904m_notify_disconnect; + redriver->r.get_orientation = nb7vpq904m_get_orientation; + redriver->r.gadget_pullup_enter = nb7vpq904m_gadget_pullup_enter; + redriver->r.gadget_pullup_exit = nb7vpq904m_gadget_pullup_exit; + redriver->r.host_powercycle = nb7vpq904m_host_powercycle; + usb_add_redriver(&redriver->r); + + return 0; +} + +static int nb7vpq904m_remove(struct i2c_client *client) +{ + struct nb7vpq904m_redriver *redriver = i2c_get_clientdata(client); + + if (usb_remove_redriver(&redriver->r)) + return -EINVAL; + + debugfs_remove_recursive(redriver->debug_root); + redriver->work_ongoing = false; + destroy_workqueue(redriver->pullup_wq); + + if (redriver->vdd) + regulator_disable(redriver->vdd); + + return 0; +} + +static ssize_t channel_config_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos, + int (*config_func)(struct nb7vpq904m_redriver *redriver, + u8 channel, u8 chan_mode, u8 val)) +{ + struct seq_file *s = file->private_data; + struct nb7vpq904m_redriver *redriver = s->private; + char buf[40]; + char *token_chan, *token_val, *this_buf; + u8 channel, chan_mode; + int ret = 0; + + memset(buf, 0, sizeof(buf)); + + this_buf = buf; + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (isdigit(buf[0])) { + ret = config_func(redriver, CHANNEL_NUM, -1, buf[0] - '0'); + if (ret < 0) + goto err; + } else if (isalpha(buf[0])) { + while ((token_chan = strsep(&this_buf, " ")) != NULL) { + switch (*token_chan) { + case 'A': + case 'B': + case 'C': + case 'D': + channel = *token_chan - 'A'; + chan_mode = CHAN_MODE_USB; + token_val = strsep(&this_buf, " "); + if (!isdigit(*token_val)) + goto err; + break; + case 'a': + case 'b': + case 'c': + case 'd': + channel = *token_chan - 'a'; + chan_mode = CHAN_MODE_DP; + token_val = strsep(&this_buf, " "); + if (!isdigit(*token_val)) + goto err; + break; + default: + goto err; + } + + ret = config_func(redriver, channel, chan_mode, + *token_val - '0'); + if (ret < 0) + goto err; + } + } else + goto err; + + + return count; + +err: + pr_err("Used to config redriver A/B/C/D channels' parameters\n" + "A/B/C/D represent for re-driver parameters for USB\n" + "a/b/c/d represent for re-driver parameters for DP\n" + "1. Set all channels to same value(both USB and DP)\n" + "echo n > [eq|output_comp|flat_gain|loss_match]\n" + "- eq: Equalization, range 0-7\n" + "- output_comp: Output Compression, range 0-3\n" + "- loss_match: LOSS Profile Matching, range 0-3\n" + "- flat_gain: Flat Gain, range 0-3\n" + "Example: Set all channels to same EQ value\n" + "echo 1 > eq\n" + "2. Set two channels to different values leave others unchanged\n" + "echo [A|B|C|D] n [A|B|C|D] n > [eq|output_comp|flat_gain|loss_match]\n" + "Example2: USB mode: set channel B flat gain to 2, set channel C flat gain to 3\n" + "echo B 2 C 3 > flat_gain\n" + "Example3: DP mode: set channel A equalization to 6, set channel B equalization to 4\n" + "echo a 6 b 4 > eq\n"); + + return -EFAULT; +} + +static int eq_status(struct seq_file *s, void *p) +{ + struct nb7vpq904m_redriver *redriver = s->private; + + seq_puts(s, "\t\t\t A(USB)\t B(USB)\t C(USB)\t D(USB)\t" + "A(DP)\t B(DP)\t C(DP)\t D(DP)\n"); + seq_printf(s, "Equalization:\t\t %d\t %d\t %d\t %d\t" + "%d\t %d\t %d\t %d\n", + redriver->eq[CHAN_MODE_USB][CHNA_INDEX], + redriver->eq[CHAN_MODE_USB][CHNB_INDEX], + redriver->eq[CHAN_MODE_USB][CHNC_INDEX], + redriver->eq[CHAN_MODE_USB][CHND_INDEX], + redriver->eq[CHAN_MODE_DP][CHNA_INDEX], + redriver->eq[CHAN_MODE_DP][CHNB_INDEX], + redriver->eq[CHAN_MODE_DP][CHNC_INDEX], + redriver->eq[CHAN_MODE_DP][CHND_INDEX]); + return 0; +} + +static int eq_status_open(struct inode *inode, + struct file *file) +{ + return single_open(file, eq_status, inode->i_private); +} + +static ssize_t eq_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + return channel_config_write(file, ubuf, count, ppos, + nb7vpq904m_eq_config); +} + +static const struct file_operations eq_ops = { + .open = eq_status_open, + .read = seq_read, + .write = eq_write, +}; + +static int flat_gain_status(struct seq_file *s, void *p) +{ + struct nb7vpq904m_redriver *redriver = s->private; + + seq_puts(s, "\t\t\t A(USB)\t B(USB)\t C(USB)\t D(USB)\t" + "A(DP)\t B(DP)\t C(DP)\t D(DP)\n"); + seq_printf(s, "TX/RX Flat Gain:\t %d\t %d\t %d\t %d\t" + "%d\t %d\t %d\t %d\n", + redriver->flat_gain[CHAN_MODE_USB][CHNA_INDEX], + redriver->flat_gain[CHAN_MODE_USB][CHNB_INDEX], + redriver->flat_gain[CHAN_MODE_USB][CHNC_INDEX], + redriver->flat_gain[CHAN_MODE_USB][CHND_INDEX], + redriver->flat_gain[CHAN_MODE_DP][CHNA_INDEX], + redriver->flat_gain[CHAN_MODE_DP][CHNB_INDEX], + redriver->flat_gain[CHAN_MODE_DP][CHNC_INDEX], + redriver->flat_gain[CHAN_MODE_DP][CHND_INDEX]); + return 0; +} + +static int flat_gain_status_open(struct inode *inode, + struct file *file) +{ + return single_open(file, flat_gain_status, inode->i_private); +} + +static ssize_t flat_gain_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + return channel_config_write(file, ubuf, count, ppos, + nb7vpq904m_flat_gain_config); +} + +static const struct file_operations flat_gain_ops = { + .open = flat_gain_status_open, + .read = seq_read, + .write = flat_gain_write, +}; + +static int output_comp_status(struct seq_file *s, void *p) +{ + struct nb7vpq904m_redriver *redriver = s->private; + + seq_puts(s, "\t\t\t A(USB)\t B(USB)\t C(USB)\t D(USB)\t" + "A(DP)\t B(DP)\t C(DP)\t D(DP)\n"); + seq_printf(s, "Output Compression:\t %d\t %d\t %d\t %d\t" + "%d\t %d\t %d\t %d\n", + redriver->output_comp[CHAN_MODE_USB][CHNA_INDEX], + redriver->output_comp[CHAN_MODE_USB][CHNB_INDEX], + redriver->output_comp[CHAN_MODE_USB][CHNC_INDEX], + redriver->output_comp[CHAN_MODE_USB][CHND_INDEX], + redriver->output_comp[CHAN_MODE_DP][CHNA_INDEX], + redriver->output_comp[CHAN_MODE_DP][CHNB_INDEX], + redriver->output_comp[CHAN_MODE_DP][CHNC_INDEX], + redriver->output_comp[CHAN_MODE_DP][CHND_INDEX]); + return 0; +} + +static int output_comp_status_open(struct inode *inode, + struct file *file) +{ + return single_open(file, output_comp_status, inode->i_private); +} + +static ssize_t output_comp_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + return channel_config_write(file, ubuf, count, ppos, + nb7vpq904m_output_comp_config); +} + +static const struct file_operations output_comp_ops = { + .open = output_comp_status_open, + .read = seq_read, + .write = output_comp_write, +}; + +static int loss_match_status(struct seq_file *s, void *p) +{ + struct nb7vpq904m_redriver *redriver = s->private; + + seq_puts(s, "\t\t\t A(USB)\t B(USB)\t C(USB)\t D(USB)\t" + "A(DP)\t B(DP)\t C(DP)\t D(DP)\n"); + seq_printf(s, "Loss Profile Match:\t %d\t %d\t %d\t %d\t" + "%d\t %d\t %d\t %d\n", + redriver->loss_match[CHAN_MODE_USB][CHNA_INDEX], + redriver->loss_match[CHAN_MODE_USB][CHNB_INDEX], + redriver->loss_match[CHAN_MODE_USB][CHNC_INDEX], + redriver->loss_match[CHAN_MODE_USB][CHND_INDEX], + redriver->loss_match[CHAN_MODE_DP][CHNA_INDEX], + redriver->loss_match[CHAN_MODE_DP][CHNB_INDEX], + redriver->loss_match[CHAN_MODE_DP][CHNC_INDEX], + redriver->loss_match[CHAN_MODE_DP][CHND_INDEX]); + return 0; +} + +static int loss_match_status_open(struct inode *inode, + struct file *file) +{ + return single_open(file, loss_match_status, inode->i_private); +} + +static ssize_t loss_match_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + return channel_config_write(file, ubuf, count, ppos, + nb7vpq904m_loss_match_config); +} + +static const struct file_operations loss_match_ops = { + .open = loss_match_status_open, + .read = seq_read, + .write = loss_match_write, +}; + +static int orientation_gpio_read(void *data, u64 *val) +{ + struct nb7vpq904m_redriver *redriver = data; + + *val = nb7vpq904m_get_orientation(&redriver->r); + + dev_dbg(redriver->dev, "orientation %llu\n", *val); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(orientation_gpio_ops, + orientation_gpio_read, NULL, "%llu\n"); + +static void nb7vpq904m_debugfs_entries( + struct nb7vpq904m_redriver *redriver) +{ + redriver->debug_root = debugfs_create_dir("nb7vpq904m_redriver", NULL); + if (!redriver->debug_root) { + dev_warn(redriver->dev, "Couldn't create debug dir\n"); + return; + } + + debugfs_create_file("eq", 0600, + redriver->debug_root, redriver, &eq_ops); + + debugfs_create_file("flat_gain", 0600, + redriver->debug_root, redriver, &flat_gain_ops); + + debugfs_create_file("output_comp", 0600, + redriver->debug_root, redriver, &output_comp_ops); + + debugfs_create_file("loss_match", 0600, + redriver->debug_root, redriver, &loss_match_ops); + + debugfs_create_file("orientation-gpio", 0444, + redriver->debug_root, redriver, &orientation_gpio_ops); + + debugfs_create_bool("lane-channel-swap", 0644, + redriver->debug_root, &redriver->lane_channel_swap); +} + +static int __maybe_unused nb7vpq904m_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct nb7vpq904m_redriver *redriver = i2c_get_clientdata(client); + + dev_dbg(redriver->dev, "%s: SS USB redriver suspend.\n", + __func__); + + /* + * 1. when in 4 lanes display mode, it can't disable; + * 2. when in NONE mode, there is no need to re-disable; + * 3. when in DEFAULT mode, there is no adsp and can't disable; + */ + if (redriver->op_mode == OP_MODE_DP || + redriver->op_mode == OP_MODE_NONE || + redriver->op_mode == OP_MODE_DEFAULT) + return 0; + + nb7vpq904m_reg_set(redriver, GEN_DEV_SET_REG, + redriver->gen_dev_val & ~CHIP_EN); + + return 0; +} + +static int __maybe_unused nb7vpq904m_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct nb7vpq904m_redriver *redriver = i2c_get_clientdata(client); + + dev_dbg(redriver->dev, "%s: SS USB redriver resume.\n", + __func__); + + /* no suspend happen in following mode */ + if (redriver->op_mode == OP_MODE_DP || + redriver->op_mode == OP_MODE_NONE || + redriver->op_mode == OP_MODE_DEFAULT) + return 0; + + nb7vpq904m_reg_set(redriver, GEN_DEV_SET_REG, + redriver->gen_dev_val); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(nb7vpq904m_pm, nb7vpq904m_suspend, + nb7vpq904m_resume); + +static void nb7vpq904m_shutdown(struct i2c_client *client) +{ + struct nb7vpq904m_redriver *redriver = i2c_get_clientdata(client); + int ret; + + /* Set back to USB mode with four channel enabled */ + ret = nb7vpq904m_reg_set(redriver, GEN_DEV_SET_REG, + GEN_DEV_SET_REG_DEFAULT); + if (ret < 0) + dev_err(&client->dev, + "%s: fail to set USB mode with 4 channel enabled.\n", + __func__); + else + dev_dbg(&client->dev, + "%s: successfully set back to USB mode.\n", + __func__); +} + +static const struct of_device_id nb7vpq904m_match_table[] = { + { .compatible = "onnn,redriver" }, + { } +}; + +static struct i2c_driver nb7vpq904m_driver = { + .driver = { + .name = "ssusb-redriver", + .of_match_table = nb7vpq904m_match_table, + .pm = &nb7vpq904m_pm, + }, + + .probe = nb7vpq904m_probe, + .remove = nb7vpq904m_remove, + .shutdown = nb7vpq904m_shutdown, +}; + +module_i2c_driver(nb7vpq904m_driver); + +MODULE_DESCRIPTION("USB Super Speed Linear Re-Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/redriver/redriver.c b/drivers/usb/redriver/redriver.c new file mode 100644 index 000000000000..a6bbfb451e8a --- /dev/null +++ b/drivers/usb/redriver/redriver.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * USB Super Speed (Plus) redriver core module + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "redriver-core: " fmt + +#include +#include + +static LIST_HEAD(usb_redriver_list); +static DEFINE_SPINLOCK(usb_rediver_lock); + +/** + * usb_add_redriver() - register a redriver from a specific chip driver. + * @redriver: redriver allocated by specific chip driver. + * + * Return: + * -EINVAL - if of node not exist + * 0 - if exist, add redriver to global list. + */ +int usb_add_redriver(struct usb_redriver *redriver) +{ + struct usb_redriver *iter; + + if (!redriver->of_node || redriver->bounded || + (redriver->has_orientation && !redriver->get_orientation)) + return -EINVAL; + + spin_lock(&usb_rediver_lock); + + list_for_each_entry(iter, &usb_redriver_list, list) { + if (iter == redriver) { + spin_unlock(&usb_rediver_lock); + return -EEXIST; + } + } + + pr_debug("add redriver %s\n", of_node_full_name(redriver->of_node)); + list_add_tail(&redriver->list, &usb_redriver_list); + spin_unlock(&usb_rediver_lock); + + return 0; +} +EXPORT_SYMBOL(usb_add_redriver); + +/** + * usb_remove_redriver() - remove a redriver from a specific chip driver. + * @redriver: redriver allocated by specific chip driver. + * + * remove redriver from global list. + * if redriver rmmod, it is better change to default state inside it's driver, + * no unbind operation here. + * + * Return: + * -EINVAL - redriver still used by uppper layer. + * 0 - redriver removed. + */ +int usb_remove_redriver(struct usb_redriver *redriver) +{ + spin_lock(&usb_rediver_lock); + + if (redriver->bounded) { + spin_unlock(&usb_rediver_lock); + return -EINVAL; + } + + pr_debug("remove redriver %s\n", of_node_full_name(redriver->of_node)); + list_del(&redriver->list); + + spin_unlock(&usb_rediver_lock); + + return 0; +} +EXPORT_SYMBOL(usb_remove_redriver); + +/** + * usb_get_redriver_by_phandle() - find redriver to be used. + * @np: device node of device which use the redriver + * @phandle_name: phandle name which refer to the redriver + * @index: phandle index which refer to the redriver + * + * Return: + * NULL - if no phandle or redriver device tree status is disabled. + * ERR_PTR(-EPROBE_DEFER) - if redriver is not registered + * if redriver registered, return pointer of it. + */ +struct usb_redriver *usb_get_redriver_by_phandle(const struct device_node *np, + const char *phandle_name, int index) +{ + struct usb_redriver *redriver; + struct device_node *node; + bool found = false; + + node = of_parse_phandle(np, phandle_name, index); + if (!of_device_is_available(node)) { + of_node_put(node); + return NULL; + } + + spin_lock(&usb_rediver_lock); + list_for_each_entry(redriver, &usb_redriver_list, list) { + if (redriver->of_node == node) { + found = true; + break; + } + } + + if (!found) { + of_node_put(node); + spin_unlock(&usb_rediver_lock); + return ERR_PTR(-EPROBE_DEFER); + } + + pr_debug("get redriver %s\n", of_node_full_name(redriver->of_node)); + redriver->bounded = true; + + spin_unlock(&usb_rediver_lock); + + return redriver; +} +EXPORT_SYMBOL(usb_get_redriver_by_phandle); + +/** + * usb_put_redriver() - redriver will not be used. + * @redriver: redriver allocated by specific chip driver. + * + * when user module exit, unbind redriver. + */ +void usb_put_redriver(struct usb_redriver *redriver) +{ + if (!redriver) + return; + + spin_lock(&usb_rediver_lock); + of_node_put(redriver->of_node); + pr_debug("put redriver %s\n", of_node_full_name(redriver->of_node)); + redriver->bounded = false; + spin_unlock(&usb_rediver_lock); + + if (redriver->unbind) + redriver->unbind(redriver); +} +EXPORT_SYMBOL(usb_put_redriver); + +/* note: following exported symbol can be inlined in header file, + * export here to avoid unexpected CFI(Clang Control Flow Integrity) issue. + */ +void usb_redriver_release_lanes(struct usb_redriver *ur, int ort, int num) +{ + if (ur && ur->release_usb_lanes) + ur->release_usb_lanes(ur, ort, num); +} +EXPORT_SYMBOL(usb_redriver_release_lanes); + +void usb_redriver_notify_connect(struct usb_redriver *ur, int ort) +{ + if (ur && ur->notify_connect) + ur->notify_connect(ur, ort); +} +EXPORT_SYMBOL(usb_redriver_notify_connect); + +void usb_redriver_notify_disconnect(struct usb_redriver *ur) +{ + if (ur && ur->notify_disconnect) + ur->notify_disconnect(ur); +} +EXPORT_SYMBOL(usb_redriver_notify_disconnect); + +int usb_redriver_get_orientation(struct usb_redriver *ur) +{ + if (ur && ur->has_orientation) + return ur->get_orientation(ur); + + return -EOPNOTSUPP; +} +EXPORT_SYMBOL(usb_redriver_get_orientation); + +void usb_redriver_gadget_pullup_enter(struct usb_redriver *ur, + int is_on) +{ + if (ur && ur->gadget_pullup_enter) + ur->gadget_pullup_enter(ur, is_on); +} +EXPORT_SYMBOL(usb_redriver_gadget_pullup_enter); + +void usb_redriver_gadget_pullup_exit(struct usb_redriver *ur, + int is_on) +{ + if (ur && ur->gadget_pullup_exit) + ur->gadget_pullup_exit(ur, is_on); +} +EXPORT_SYMBOL(usb_redriver_gadget_pullup_exit); + +void usb_redriver_host_powercycle(struct usb_redriver *ur) +{ + if (ur && ur->host_powercycle) + ur->host_powercycle(ur); +} +EXPORT_SYMBOL(usb_redriver_host_powercycle); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("USB Super Speed (Plus) redriver core module"); diff --git a/include/linux/usb/redriver.h b/include/linux/usb/redriver.h index 37a56370f1c9..60b1a05ed8ea 100644 --- a/include/linux/usb/redriver.h +++ b/include/linux/usb/redriver.h @@ -40,10 +40,10 @@ * @release_usb_lanes: put redriver into 2/4 lanes display mode * @notify_connect: cable connect * @notify_disconnect: cable disconnect - * @orientation_get: report orientation to user if orientation source shared + * @get_orientation: report orientation to user if orientation source shared * @gadget_pullup_enter: operation when enter gadget pullup function * @gadget_pullup_exit: operation when exit gadget pullup function - * @host_power_cycle: workaround for host otg case + * @host_powercycle: workaround for host otg case * @unbind, change to default state when user unbind it * @has_orientation, provide orientation from chip driver or not * @bounded, bound to user or not @@ -52,7 +52,7 @@ struct usb_redriver { struct list_head list; struct device_node *of_node; - int (*release_usb_lanes)(struct usb_redriver *ur, int num); + int (*release_usb_lanes)(struct usb_redriver *ur, int ort, int num); int (*notify_connect)(struct usb_redriver *ur, int ort); int (*notify_disconnect)(struct usb_redriver *ur); int (*get_orientation)(struct usb_redriver *ur); @@ -73,7 +73,7 @@ struct usb_redriver *usb_get_redriver_by_phandle( const struct device_node *np, const char *phandle_name, int index); void usb_put_redriver(struct usb_redriver *ur); -void usb_redriver_release_lanes(struct usb_redriver *ur, int num); +void usb_redriver_release_lanes(struct usb_redriver *ur, int ort, int num); void usb_redriver_notify_connect(struct usb_redriver *ur, int ort); void usb_redriver_notify_disconnect(struct usb_redriver *ur); int usb_redriver_get_orientation(struct usb_redriver *ur); @@ -102,12 +102,12 @@ static inline int usb_remove_redriver(struct usb_redriver *ur) static inline int usb_redriver_get_orientation(struct usb_redriver *ur) { - return -1; + return -EOPNOTSUPP; } #define usb_put_redriver(ur) do {} while (0) -#define usb_redriver_release_lanes(ur, num) do {} while (0) +#define usb_redriver_release_lanes(ur, ort, num) do {} while (0) #define usb_redriver_notify_connect(ur, ort) do {} while (0) #define usb_redriver_notify_disconnect(ur) do {} while (0) #define usb_redriver_gadget_pullup_enter(ur, is_on) do {} while (0)