android_kernel_xiaomi_sm7250/drivers/usb/misc/ssusb-redriver-nb7vpq904m.c
xiangxuy d41d516ea0 msm: disp: dp: add aux dp support by usb mode
dp aux config change by usb mode & orientation.

Change-Id: Ice36032ad5f7d1c67cf79f43a26fe73726df955b
Signed-off-by: xiangxuy <quic_xiangxuy@quicinc.com>
2022-07-15 14:08:19 +08:00

1258 lines
33 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2018-2021, The Linux Foundation. All rights reserved.
*/
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/extcon.h>
#include <linux/power_supply.h>
#include <linux/usb/ch9.h>
#include <linux/debugfs.h>
#include <linux/uaccess.h>
#include <linux/regmap.h>
#include <linux/ctype.h>
/* 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 0x03
#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
#define CHAN_MODE_NUM 2
/* for type c cable */
enum plug_orientation {
ORIENTATION_NONE,
ORIENTATION_CC1,
ORIENTATION_CC2,
};
/*
* Three Modes of Operations:
* - One/Two ports of USB 3.1 Gen1/Gen2 (Default Mode)
* - Two lanes of DisplayPort 1.4 + One port of USB 3.1 Gen1/Gen2
* - Four lanes of DisplayPort 1.4
*/
enum operation_mode {
OP_MODE_USB, /* One/Two ports of USB */
OP_MODE_DP, /* DP 4 Lane and DP 2 Lane */
OP_MODE_USB_AND_DP, /* One port of USB and DP 2 Lane */
};
/*
* USB redriver channel mode:
* - USB mode
* - DP mode
*/
enum channel_mode {
CHAN_MODE_USB,
CHAN_MODE_DP,
};
/**
* struct ssusb_redriver - representation of USB re-driver
* @dev: struct device pointer
* @regmap: used for I2C communication on accessing registers
* @client: i2c client structure pointer
* @config_work: used to configure re-driver
* @redriver_wq: work queue used for @config_work
* @usb_psy: structure that holds USB power supply status
* @host_active: used to indicate USB host mode is enabled or not
* @vbus_active: used to indicate USB device mode is enabled or not
* @is_usb3: used to indicate USB3 or not
* @typec_orientation: used to inditate Type C orientation
* @op_mode: used to store re-driver operation mode
* @extcon_usb: external connector used for USB host/device mode
* @extcon_dp: external connector used for DP
* @vbus_nb: used for vbus event reception
* @id_nb: used for id event reception
* @dp_nb: used for DP event reception
* @panic_nb: used for panic event reception
* @chan_mode: used to indicate re-driver's channel mode
* @eq: equalization register value.
* eq[0] - eq[3]: Channel A-D parameter for USB
* eq[4] - eq[7]: Channel A-D parameter for DP
* @output_comp: output compression register value
* output_comp[0] - output_comp[3]: Channel A-D parameter for USB
* output_comp[4] - output_comp[7]: Channel A-D parameter for DP
* @loss_match: loss profile matching control register value
* loss_match[0] - loss_match[3]: Channel A-D parameter for USB
* loss_match[4] - loss_match[7]: Channel A-D parameter for DP
* @flat_gain: flat gain control register value
* flat_gain[0] - flat_gain[3]: Channel A-D parameter for USB
* flat_gain[4] - flat_gain[7]: Channel A-D parameter for DP
* @debug_root: debugfs entry for this context
*/
struct ssusb_redriver {
struct device *dev;
struct regmap *regmap;
struct i2c_client *client;
struct work_struct config_work;
struct workqueue_struct *redriver_wq;
struct power_supply *usb_psy;
bool host_active;
bool vbus_active;
bool is_usb3;
bool is_set_aux;
enum plug_orientation typec_orientation;
enum operation_mode op_mode;
struct extcon_dev *extcon_usb;
struct extcon_dev *extcon_dp;
struct notifier_block vbus_nb;
struct notifier_block id_nb;
struct notifier_block dp_nb;
struct notifier_block panic_nb;
enum channel_mode 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;
struct dentry *debug_root;
};
static int ssusb_redriver_channel_update(struct ssusb_redriver *redriver);
static void ssusb_redriver_debugfs_entries(struct ssusb_redriver *redriver);
static int redriver_i2c_reg_get(struct ssusb_redriver *redriver,
u8 reg, u8 *val)
{
int ret;
unsigned int val_tmp;
ret = regmap_read(redriver->regmap, (unsigned int)reg, &val_tmp);
if (ret < 0) {
dev_err(redriver->dev, "reading reg 0x%02x failure\n", reg);
return ret;
}
*val = (u8)val_tmp;
dev_dbg(redriver->dev, "reading reg 0x%02x=0x%02x\n", reg, *val);
return 0;
}
static int redriver_i2c_reg_set(struct ssusb_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;
}
/**
* Handle Re-driver chip operation mode and channel settings.
*
* Three Modes of Operations:
* - One/Two ports of USB 3.1 Gen1/Gen2 (Default Mode)
* - Two lanes of DisplayPort 1.4 + One port of USB 3.1 Gen1/Gen2
* - Four lanes of DisplayPort 1.4
*
* @redriver - contain redriver status
* @on - re-driver chip enable or not
*/
static void ssusb_redriver_gen_dev_set(
struct ssusb_redriver *redriver, bool on)
{
int ret;
u8 val, oldval;
u8 aux_val = AUX_DISABLE_VAL;
val = 0;
switch (redriver->op_mode) {
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);
} else {
/* Enable channel A, B, C and D */
val |= (CHNA_EN | CHNB_EN);
val |= (CHNC_EN | CHND_EN);
}
/* Set to default USB Mode */
val |= (0x5 << OP_MODE_SHIFT);
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);
if (redriver->typec_orientation
== ORIENTATION_CC1) {
aux_val = AUX_NORMAL_VAL;
} else {
aux_val = AUX_FLIP_VAL;
}
break;
case OP_MODE_USB_AND_DP:
/* Enable channel A, B, C and D */
val |= (CHNA_EN | CHNB_EN);
val |= (CHNC_EN | CHND_EN);
if (redriver->typec_orientation
== ORIENTATION_CC1) {
/* Set to DP 4 Lane Mode (OP Mode 1) */
val |= (0x1 << OP_MODE_SHIFT);
aux_val = AUX_NORMAL_VAL;
} else {
/* Set to DP 4 Lane Mode (OP Mode 0) */
val |= (0x0 << OP_MODE_SHIFT);
aux_val = AUX_FLIP_VAL;
}
break;
default:
dev_err(redriver->dev,
"Error: op mode: %d, vbus: %d, host: %d.\n",
redriver->op_mode, redriver->vbus_active,
redriver->host_active);
goto err_exit;
}
/* exit/enter deep-sleep power mode */
oldval = redriver->gen_dev_val;
if (on) {
val |= CHIP_EN;
if (val == oldval)
return;
} else {
/* no operation if already disabled */
if (oldval && !(oldval & CHIP_EN))
return;
val &= ~CHIP_EN;
}
ret = redriver_i2c_reg_set(redriver, GEN_DEV_SET_REG, val);
if (ret < 0)
goto err_exit;
dev_dbg(redriver->dev,
"successfully (%s) the redriver chip, reg 0x00 = 0x%x\n",
on ? "ENABLE":"DISABLE", val);
redriver->gen_dev_val = val;
if (redriver->is_set_aux) {
ret = redriver_i2c_reg_set(redriver, AUX_SWITCH_REG, aux_val);
if (ret < 0) {
dev_err(redriver->dev,
"failure to %s set AUX, aux_val = 0x%x\n",
on ? "ENABLE":"DISABLE", val, aux_val);
return;
}
dev_dbg(redriver->dev,
"successfully (%s) set AUX, aux_val = 0x%x\n",
on ? "ENABLE":"DISABLE", aux_val);
}
return;
err_exit:
dev_err(redriver->dev,
"failure to (%s) the redriver chip, reg 0x00 = 0x%x\n",
on ? "ENABLE":"DISABLE", val);
}
static void ssusb_redriver_config_work(struct work_struct *w)
{
struct ssusb_redriver *redriver = container_of(w,
struct ssusb_redriver, config_work);
struct extcon_dev *edev = NULL;
union extcon_property_value val;
unsigned int extcon_id = EXTCON_NONE;
int ret = 0;
dev_dbg(redriver->dev, "%s: USB SS redriver config work\n",
__func__);
edev = redriver->extcon_usb;
if (redriver->vbus_active)
extcon_id = EXTCON_USB;
else if (redriver->host_active)
extcon_id = EXTCON_USB_HOST;
if (edev && (extcon_id != EXTCON_NONE)
&& extcon_get_state(edev, extcon_id)) {
ret = extcon_get_property(edev, extcon_id,
EXTCON_PROP_USB_SS, &val);
if (!ret) {
redriver->is_usb3 = (val.intval != 0);
dev_dbg(redriver->dev, "SS Lane is used? [%s].\n",
redriver->is_usb3 ? "true" : "false");
} else {
redriver->is_usb3 = true;
dev_dbg(redriver->dev, "Default true as speed isn't reported.\n");
}
if (redriver->is_usb3 || (redriver->op_mode != OP_MODE_USB)) {
ret = extcon_get_property(edev, extcon_id,
EXTCON_PROP_USB_TYPEC_POLARITY, &val);
if (!ret)
redriver->typec_orientation = val.intval ?
ORIENTATION_CC2 : ORIENTATION_CC1;
else if (redriver->op_mode == OP_MODE_USB)
redriver->typec_orientation = ORIENTATION_NONE;
else
dev_err(redriver->dev, "fail to get orientation when has DP.\n");
ssusb_redriver_gen_dev_set(redriver, true);
} else {
dev_dbg(redriver->dev,
"Disable chip when not in SS USB mode.\n");
ssusb_redriver_gen_dev_set(redriver, false);
}
dev_dbg(redriver->dev, "Type C orientation code is %d.\n",
redriver->typec_orientation);
} else if (redriver->op_mode != OP_MODE_USB) {
/*
* USB host stack will be turned off if peer doesn't
* support USB communication. PD driver will send
* id notification when disable host stack. Update
* redriver channel mode when operation mode changed.
*/
dev_dbg(redriver->dev,
"Update redriver operation mode.\n");
ssusb_redriver_gen_dev_set(redriver, true);
} else {
dev_dbg(redriver->dev, "USB Cable is disconnected.\n");
/* Set back to USB only mode when cable disconnect */
redriver->op_mode = OP_MODE_USB;
ssusb_redriver_gen_dev_set(redriver, false);
}
}
static int ssusb_redriver_dp_notifier(struct notifier_block *nb,
unsigned long dp_lane, void *ptr)
{
struct ssusb_redriver *redriver = container_of(nb,
struct ssusb_redriver, dp_nb);
enum operation_mode op_mode;
int ret = 0;
dev_dbg(redriver->dev,
"redriver op mode change: %ld event received\n", dp_lane);
switch (dp_lane) {
case 0:
op_mode = OP_MODE_USB;
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 2:
op_mode = 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 4:
op_mode = 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;
}
if (redriver->op_mode == op_mode)
return 0;
redriver->op_mode = op_mode;
ret = ssusb_redriver_channel_update(redriver);
if (ret)
dev_dbg(redriver->dev,
"redriver channel mode change will continue\n");
queue_work(redriver->redriver_wq, &redriver->config_work);
return 0;
}
static int ssusb_redriver_vbus_notifier(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct ssusb_redriver *redriver = container_of(nb,
struct ssusb_redriver, vbus_nb);
dev_dbg(redriver->dev, "vbus:%ld event received\n", event);
if (redriver->vbus_active == event)
return NOTIFY_DONE;
redriver->vbus_active = event;
queue_work(redriver->redriver_wq, &redriver->config_work);
return NOTIFY_DONE;
}
static int ssusb_redriver_id_notifier(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct ssusb_redriver *redriver = container_of(nb,
struct ssusb_redriver, id_nb);
bool host_active = (bool)event;
dev_dbg(redriver->dev, "host_active:%s event received\n",
host_active ? "true" : "false");
if (redriver->host_active == host_active)
return NOTIFY_DONE;
redriver->host_active = host_active;
queue_work(redriver->redriver_wq, &redriver->config_work);
return NOTIFY_DONE;
}
static int ssusb_redriver_extcon_register(struct ssusb_redriver *redriver)
{
struct device_node *node = redriver->dev->of_node;
struct extcon_dev *edev;
int ret = 0;
if (!of_find_property(node, "extcon", NULL)) {
dev_err(redriver->dev, "failed to get extcon for redriver\n");
return 0;
}
edev = extcon_get_edev_by_phandle(redriver->dev, 0);
if (IS_ERR(edev) && PTR_ERR(edev) != -ENODEV) {
dev_err(redriver->dev, "failed to get phandle for redriver\n");
return PTR_ERR(edev);
}
if (!IS_ERR(edev)) {
redriver->extcon_usb = edev;
redriver->vbus_nb.notifier_call = ssusb_redriver_vbus_notifier;
redriver->vbus_nb.priority = NOTIFIER_PRIORITY;
ret = extcon_register_notifier(edev, EXTCON_USB,
&redriver->vbus_nb);
if (ret < 0) {
dev_err(redriver->dev,
"failed to register notifier for redriver\n");
return ret;
}
redriver->id_nb.notifier_call = ssusb_redriver_id_notifier;
redriver->id_nb.priority = NOTIFIER_PRIORITY;
ret = extcon_register_notifier(edev, EXTCON_USB_HOST,
&redriver->id_nb);
if (ret < 0) {
dev_err(redriver->dev,
"failed to register notifier for USB-HOST\n");
goto err;
}
}
edev = NULL;
/* Use optional phandle (index 1) for DP lane events */
if (of_count_phandle_with_args(node, "extcon", NULL) > 1) {
edev = extcon_get_edev_by_phandle(redriver->dev, 1);
if (IS_ERR(edev) && PTR_ERR(edev) != -ENODEV) {
ret = PTR_ERR(edev);
goto err1;
}
}
if (!IS_ERR_OR_NULL(edev)) {
redriver->extcon_dp = edev;
redriver->dp_nb.notifier_call =
ssusb_redriver_dp_notifier;
redriver->dp_nb.priority = NOTIFIER_PRIORITY;
ret = extcon_register_blocking_notifier(edev, EXTCON_DISP_DP,
&redriver->dp_nb);
if (ret < 0) {
dev_err(redriver->dev,
"failed to register blocking notifier\n");
goto err1;
}
}
/* Update initial VBUS/ID state from extcon */
if (extcon_get_state(redriver->extcon_usb, EXTCON_USB))
ssusb_redriver_vbus_notifier(&redriver->vbus_nb, true,
redriver->extcon_usb);
else if (extcon_get_state(redriver->extcon_usb, EXTCON_USB_HOST))
ssusb_redriver_id_notifier(&redriver->id_nb, true,
redriver->extcon_usb);
return 0;
err1:
if (redriver->extcon_usb)
extcon_unregister_notifier(redriver->extcon_usb,
EXTCON_USB_HOST, &redriver->id_nb);
err:
if (redriver->extcon_usb)
extcon_unregister_notifier(redriver->extcon_usb,
EXTCON_USB, &redriver->vbus_nb);
return ret;
}
static int ssusb_redriver_param_config(struct ssusb_redriver *redriver,
u8 reg_base, u8 channel, u8 mask, u8 shift, u8 val,
u8 (*stored_val)[CHANNEL_NUM])
{
int i, j, ret = -EINVAL;
u8 reg_addr, reg_val, real_channel, chan_mode;
if (channel == CHANNEL_NUM * CHAN_MODE_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);
ret = redriver_i2c_reg_get(redriver,
reg_addr, &reg_val);
if (ret < 0)
return ret;
reg_val &= ~(mask << shift);
reg_val |= (val << shift);
ret = redriver_i2c_reg_set(redriver,
reg_addr, reg_val);
if (ret < 0)
return ret;
}
stored_val[i][j] = val;
}
} else if (channel < CHANNEL_NUM * CHAN_MODE_NUM) {
real_channel = channel % CHANNEL_NUM;
chan_mode = channel / CHANNEL_NUM;
if (redriver->chan_mode[real_channel] == chan_mode) {
reg_addr = reg_base + (real_channel << 1);
ret = redriver_i2c_reg_get(redriver,
reg_addr, &reg_val);
if (ret < 0)
return ret;
reg_val &= ~(mask << shift);
reg_val |= (val << shift);
ret = redriver_i2c_reg_set(redriver,
reg_addr, reg_val);
if (ret < 0)
return ret;
}
stored_val[chan_mode][real_channel] = val;
} else {
dev_err(redriver->dev, "error channel value.\n");
return ret;
}
return 0;
}
static int ssusb_redriver_eq_config(
struct ssusb_redriver *redriver, u8 channel, u8 val)
{
if (val <= EQ_SETTING_MASK)
return ssusb_redriver_param_config(redriver,
EQ_SET_REG_BASE, channel, EQ_SETTING_MASK,
EQ_SETTING_SHIFT, val, redriver->eq);
else
return -EINVAL;
}
static int ssusb_redriver_flat_gain_config(
struct ssusb_redriver *redriver, u8 channel, u8 val)
{
if (val <= FLAT_GAIN_MASK)
return ssusb_redriver_param_config(redriver,
FLAT_GAIN_REG_BASE, channel, FLAT_GAIN_MASK,
FLAT_GAIN_SHIFT, val, redriver->flat_gain);
else
return -EINVAL;
}
static int ssusb_redriver_output_comp_config(
struct ssusb_redriver *redriver, u8 channel, u8 val)
{
if (val <= OUTPUT_COMPRESSION_MASK)
return ssusb_redriver_param_config(redriver,
OUT_COMP_AND_POL_REG_BASE, channel,
OUTPUT_COMPRESSION_MASK,
OUTPUT_COMPRESSION_SHIFT, val,
redriver->output_comp);
else
return -EINVAL;
}
static int ssusb_redriver_loss_match_config(
struct ssusb_redriver *redriver, u8 channel, u8 val)
{
if (val <= LOSS_MATCH_MASK)
return ssusb_redriver_param_config(redriver,
LOSS_MATCH_REG_BASE, channel, LOSS_MATCH_MASK,
LOSS_MATCH_SHIFT, val, redriver->loss_match);
else
return -EINVAL;
}
static int ssusb_redriver_channel_update(struct ssusb_redriver *redriver)
{
int ret = 0, i = 0, pos = 0;
u8 chan_mode;
for (i = 0; i < CHANNEL_NUM; i++) {
chan_mode = redriver->chan_mode[i];
pos = i + chan_mode * CHANNEL_NUM;
ret = ssusb_redriver_eq_config(redriver, pos,
redriver->eq[chan_mode][i]);
if (ret)
goto err;
ret = ssusb_redriver_flat_gain_config(redriver, pos,
redriver->flat_gain[chan_mode][i]);
if (ret)
goto err;
ret = ssusb_redriver_output_comp_config(redriver, pos,
redriver->output_comp[chan_mode][i]);
if (ret)
goto err;
ret = ssusb_redriver_loss_match_config(redriver, pos,
redriver->loss_match[chan_mode][i]);
if (ret)
goto err;
}
dev_dbg(redriver->dev, "redriver channel parameters updated.\n");
return 0;
err:
dev_err(redriver->dev, "channel parameters update failure.\n");
return ret;
}
static int ssusb_redriver_default_config(struct ssusb_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-enable");
ret = ssusb_redriver_channel_update(redriver);
if (ret)
goto err;
return 0;
err:
dev_err(redriver->dev,
"%s: set default parameters failure.\n", __func__);
return ret;
}
static int ssusb_redriver_panic_notifier(struct notifier_block *this,
unsigned long event, void *ptr)
{
struct ssusb_redriver *redriver = container_of(this,
struct ssusb_redriver, panic_nb);
pr_err("%s: op mode: %d, vbus: %d, host: %d\n", __func__,
redriver->op_mode, redriver->vbus_active,
redriver->host_active);
return NOTIFY_OK;
}
static const struct regmap_config redriver_regmap = {
.max_register = REDRIVER_REG_MAX,
.reg_bits = 8,
.val_bits = 8,
};
static int redriver_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *dev_id)
{
struct ssusb_redriver *redriver;
union power_supply_propval pval = {0};
int ret;
redriver = devm_kzalloc(&client->dev, sizeof(struct ssusb_redriver),
GFP_KERNEL);
if (!redriver)
return -ENOMEM;
INIT_WORK(&redriver->config_work, ssusb_redriver_config_work);
redriver->redriver_wq = alloc_ordered_workqueue("redriver_wq",
WQ_HIGHPRI);
if (!redriver->redriver_wq) {
dev_err(&client->dev,
"%s: Unable to create workqueue redriver_wq\n",
__func__);
return -ENOMEM;
}
redriver->dev = &client->dev;
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);
goto destroy_wq;
}
redriver->client = client;
i2c_set_clientdata(client, redriver);
/* Set default parameters for A/B/C/D channels. */
ret = ssusb_redriver_default_config(redriver);
if (ret < 0)
goto destroy_wq;
/* Set id_state as float by default*/
redriver->host_active = false;
/* Set to USB by default */
redriver->op_mode = OP_MODE_USB;
redriver->usb_psy = power_supply_get_by_name("usb");
if (!redriver->usb_psy) {
dev_warn(&client->dev, "Could not get usb power_supply\n");
pval.intval = -EINVAL;
} else {
power_supply_get_property(redriver->usb_psy,
POWER_SUPPLY_PROP_PRESENT, &pval);
/* USB cable is not connected */
if (!pval.intval)
ssusb_redriver_gen_dev_set(redriver, false);
}
ret = ssusb_redriver_extcon_register(redriver);
if (ret)
goto put_psy;
redriver->panic_nb.notifier_call = ssusb_redriver_panic_notifier;
atomic_notifier_chain_register(&panic_notifier_list,
&redriver->panic_nb);
ssusb_redriver_debugfs_entries(redriver);
dev_dbg(&client->dev, "USB 3.1 Gen1/Gen2 Re-Driver Probed.\n");
return 0;
put_psy:
if (redriver->usb_psy)
power_supply_put(redriver->usb_psy);
destroy_wq:
destroy_workqueue(redriver->redriver_wq);
return ret;
}
static int redriver_i2c_remove(struct i2c_client *client)
{
struct ssusb_redriver *redriver = i2c_get_clientdata(client);
debugfs_remove(redriver->debug_root);
atomic_notifier_chain_unregister(&panic_notifier_list,
&redriver->panic_nb);
if (redriver->usb_psy)
power_supply_put(redriver->usb_psy);
destroy_workqueue(redriver->redriver_wq);
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 ssusb_redriver *redriver,
u8 channel, u8 val))
{
struct seq_file *s = file->private_data;
struct ssusb_redriver *redriver = s->private;
char buf[40];
char *token_chan, *token_val, *this_buf;
int store_offset = 0, 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 * CHAN_MODE_NUM,
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':
store_offset = *token_chan - 'A';
token_val = strsep(&this_buf, " ");
if (!isdigit(*token_val))
goto err;
break;
case 'a':
case 'b':
case 'c':
case 'd':
store_offset = *token_chan - 'a'
+ CHANNEL_NUM;
token_val = strsep(&this_buf, " ");
if (!isdigit(*token_val))
goto err;
break;
default:
goto err;
}
ret = config_func(redriver, store_offset,
*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 ssusb_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,
ssusb_redriver_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 ssusb_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,
ssusb_redriver_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 ssusb_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,
ssusb_redriver_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 ssusb_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,
ssusb_redriver_loss_match_config);
}
static const struct file_operations loss_match_ops = {
.open = loss_match_status_open,
.read = seq_read,
.write = loss_match_write,
};
static void ssusb_redriver_debugfs_entries(
struct ssusb_redriver *redriver)
{
struct dentry *ent;
redriver->debug_root = debugfs_create_dir("ssusb_redriver", NULL);
if (!redriver->debug_root) {
dev_warn(redriver->dev, "Couldn't create debug dir\n");
return;
}
ent = debugfs_create_file("eq", 0600,
redriver->debug_root, redriver, &eq_ops);
if (IS_ERR_OR_NULL(ent))
dev_warn(redriver->dev, "Couldn't create eq file\n");
ent = debugfs_create_file("flat_gain", 0600,
redriver->debug_root, redriver, &flat_gain_ops);
if (IS_ERR_OR_NULL(ent))
dev_warn(redriver->dev, "Couldn't create flat_gain file\n");
ent = debugfs_create_file("output_comp", 0600,
redriver->debug_root, redriver, &output_comp_ops);
if (IS_ERR_OR_NULL(ent))
dev_warn(redriver->dev, "Couldn't create output_comp file\n");
ent = debugfs_create_file("loss_match", 0600,
redriver->debug_root, redriver, &loss_match_ops);
if (IS_ERR_OR_NULL(ent))
dev_warn(redriver->dev, "Couldn't create loss_match file\n");
}
static int __maybe_unused redriver_i2c_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct ssusb_redriver *redriver = i2c_get_clientdata(client);
dev_dbg(redriver->dev, "%s: SS USB redriver suspend.\n",
__func__);
/* Disable redriver chip when USB cable disconnected */
if ((!redriver->vbus_active && !redriver->host_active &&
redriver->op_mode != OP_MODE_DP) ||
(redriver->host_active &&
redriver->op_mode == OP_MODE_USB_AND_DP))
ssusb_redriver_gen_dev_set(redriver, false);
flush_workqueue(redriver->redriver_wq);
return 0;
}
static int __maybe_unused redriver_i2c_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct ssusb_redriver *redriver = i2c_get_clientdata(client);
dev_dbg(redriver->dev, "%s: SS USB redriver resume.\n",
__func__);
if (redriver->host_active &&
redriver->op_mode == OP_MODE_USB_AND_DP)
ssusb_redriver_gen_dev_set(redriver, true);
flush_workqueue(redriver->redriver_wq);
return 0;
}
static SIMPLE_DEV_PM_OPS(redriver_i2c_pm, redriver_i2c_suspend,
redriver_i2c_resume);
static void redriver_i2c_shutdown(struct i2c_client *client)
{
struct ssusb_redriver *redriver = i2c_get_clientdata(client);
int ret;
/* Set back to USB mode with four channel enabled */
ret = redriver_i2c_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 redriver_match_table[] = {
{ .compatible = "onnn,redriver",},
{ },
};
static const struct i2c_device_id redriver_i2c_id[] = {
{ "ssusb redriver", 0 },
{ },
};
MODULE_DEVICE_TABLE(i2c, redriver_i2c_id);
static struct i2c_driver redriver_i2c_driver = {
.driver = {
.name = "ssusb redriver",
.of_match_table = redriver_match_table,
.pm = &redriver_i2c_pm,
},
.probe = redriver_i2c_probe,
.remove = redriver_i2c_remove,
.shutdown = redriver_i2c_shutdown,
.id_table = redriver_i2c_id,
};
module_i2c_driver(redriver_i2c_driver);
MODULE_DESCRIPTION("USB Super Speed Linear Re-Driver Driver");
MODULE_LICENSE("GPL v2");