Files
2025-08-12 22:16:57 +02:00

1038 lines
27 KiB
C
Executable File

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2018-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/usb/ch9.h>
#include <linux/debugfs.h>
#include <linux/uaccess.h>
#include <linux/regmap.h>
#include <linux/ctype.h>
#include <linux/pinctrl/consumer.h>
#include <linux/of_gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/usb/redriver.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 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 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) {
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 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)
{
int ret = 0;
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;
ret = nb7vpq904m_reg_set(redriver, GEN_DEV_SET_REG, 0);
if (!ret)
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;
nb7vpq904m_vdd_enable(redriver, true);
/* 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 const struct regmap_config redriver_regmap = {
.name = "nb7vpq904m",
.max_register = REDRIVER_REG_MAX,
.reg_bits = 8,
.val_bits = 8,
};
static int nb7vpq904m_probe(struct i2c_client *client)
{
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_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.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 void nb7vpq904m_remove(struct i2c_client *client)
{
struct nb7vpq904m_redriver *redriver = i2c_get_clientdata(client);
if (usb_remove_redriver(&redriver->r))
return;
debugfs_remove_recursive(redriver->debug_root);
redriver->work_ongoing = false;
destroy_workqueue(redriver->pullup_wq);
if (redriver->vdd)
regulator_disable(redriver->vdd);
}
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 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_bool("lane-channel-swap", 0644,
redriver->debug_root, &redriver->lane_channel_swap);
}
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,
},
.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");