1077 lines
27 KiB
C
1077 lines
27 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/usb/ch9.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/usb/redriver.h>
|
|
#include <linux/regulator/consumer.h>
|
|
|
|
/* dp eq */
|
|
#define PS5169_DP_EQ0_REG 0x52
|
|
#define PS5169_DP_EQ0_MASK 0x70
|
|
#define PS5169_DP_EQ1_REG 0x5e
|
|
#define PS5169_DP_EQ1_MASK 0x07
|
|
|
|
#define PS5169_DP_EQ_MAX 5
|
|
#define PS5169_DP_EQ_DEFAULT 0
|
|
static const struct ps5169_dp_eq {
|
|
unsigned int eq0; /* 0x52 bit 6:4 */
|
|
unsigned int eq1; /* 0x5e bit 2:0 */
|
|
} dp_eq[] = {
|
|
{0x00, 0x04}, /* 0:2db */
|
|
{0x10, 0x05}, /* 1:5.5db */
|
|
{0x20, 0x06}, /* 2:6.5db */
|
|
{0x30, 0x06}, /* 3:7.5db */
|
|
{0x40, 0x06}, /* 4:8db */
|
|
{0x50, 0x07}, /* 5:8.5db */
|
|
{0x60, 0x07}, /* 6:9.5db */
|
|
{0x70, 0x07}, /* 7:10db ??? */
|
|
};
|
|
|
|
#define PS5169_DP_EQ0_VAL(eq) dp_eq[eq].eq0
|
|
#define PS5169_DP_EQ1_VAL(eq) dp_eq[eq].eq1
|
|
|
|
/* dp gain */
|
|
#define PS5169_DP_GAIN_REG 0x5c
|
|
#define PS5169_DP_GAIN_MASK BIT(4)
|
|
#define PS5169_DP_GAIN_MAX 1 /* -0.9db */
|
|
#define PS5169_DP_GAIN_DEFAULT 0 /* 0 db */
|
|
|
|
/* usb tx eq */
|
|
#define PS5169_USB_TX_EQ0_REG 0x50
|
|
#define PS5169_USB_TX_EQ0_MASK 0x70
|
|
#define PS5169_USB_TX_EQ1_REG 0x5d
|
|
#define PS5169_USB_TX_EQ1_MASK 0x70
|
|
#define PS5169_USB_TX_EQ2_REG 0x54
|
|
#define PS5169_USB_TX_EQ2_MASK 0xf0
|
|
|
|
#define PS5169_USB_TX_EQ_MAX 7
|
|
#define PS5169_USB_TX_EQ_DEFAULT 1
|
|
static const struct ps5169_usb_tx_eq {
|
|
unsigned int eq0; /* 0x50 bit 6:4 */
|
|
unsigned int eq1; /* 0x5d bit 6:4 */
|
|
unsigned int eq2; /* 0x54 bit 7:4 */
|
|
} usb_tx_eq[] = {
|
|
{0x00, 0x40, 0x00}, /* 0:2db */
|
|
{0x10, 0x50, 0x10}, /* 1:5.5db */
|
|
{0x20, 0x60, 0x10}, /* 2:6.5db */
|
|
{0x30, 0x60, 0x50}, /* 3:7.5db */
|
|
{0x40, 0x60, 0xc0}, /* 4:8db */
|
|
{0x50, 0x70, 0x50}, /* 5:8.5db */
|
|
{0x60, 0x70, 0xf0}, /* 6:9.5db */
|
|
{0x70, 0x70, 0xf0}, /* 7:10db ???? */
|
|
};
|
|
|
|
#define PS5169_USB_TX_EQ0_VAL(eq) usb_tx_eq[eq].eq0
|
|
#define PS5169_USB_TX_EQ1_VAL(eq) usb_tx_eq[eq].eq1
|
|
#define PS5169_USB_TX_EQ2_VAL(eq) usb_tx_eq[eq].eq2
|
|
|
|
/* usb tx gain */
|
|
#define PS5169_USB_TX_GAIN_REG 0x5c
|
|
#define PS5169_USB_TX_GAIN_MASK BIT(0)
|
|
#define PS5169_USB_TX_GAIN_MAX 1 /* -0.9db */
|
|
#define PS5169_USB_TX_GAIN_DEFAULT 0 /* 0db */
|
|
|
|
/* usb rx eq */
|
|
#define PS5169_USB_RX_EQ0_REG 0x51
|
|
#define PS5169_USB_RX_EQ0_MASK 0x70
|
|
#define PS5169_USB_RX_EQ1_REG 0x77
|
|
#define PS5169_USB_RX_EQ1_MASK 0xf0
|
|
#define PS5169_USB_RX_EQ2_REG 0x54
|
|
#define PS5169_USB_RX_EQ2_MASK 0x0f
|
|
#define PS5169_USB_RX_EQ3_REG 0x78
|
|
#define PS5169_USB_RX_EQ3_MASK 0xe1
|
|
|
|
#define PS5169_USB_RX_EQ_MAX 7
|
|
static const struct ps5169_usb_rx_eq {
|
|
unsigned int eq0; /* 0x51 bit 7:4 2:1 */
|
|
unsigned int eq1; /* 0x77 bit 7:4 */
|
|
unsigned int eq2; /* 0x54 bit 3:0 */
|
|
unsigned int eq3; /* 0x78 bit 7:5 0 */
|
|
} usb_rx_eq[] = {
|
|
{0x86, 0x00, 0x00, 0x20}, /* 0:5.2db */
|
|
{0x96, 0x00, 0x01, 0x20}, /* 1:6db */
|
|
{0xa6, 0x50, 0x01, 0x40}, /* 2:7db */
|
|
{0xb6, 0x50, 0x05, 0x40}, /* 3:8db */
|
|
{0xc6, 0xb0, 0x0c, 0x80}, /* 4:8.8db */
|
|
{0xd6, 0xf0, 0x05, 0x80}, /* 5:9.6db */
|
|
{0xe6, 0xf0, 0x0f, 0x80}, /* 6:10.4db */
|
|
{0xf6, 0x30, 0x0f, 0xa1}, /* 7:11.2db */
|
|
};
|
|
|
|
#define PS5169_USB_RX_EQ0_VAL(eq) usb_rx_eq[eq].eq0
|
|
#define PS5169_USB_RX_EQ1_VAL(eq) usb_rx_eq[eq].eq1
|
|
#define PS5169_USB_RX_EQ2_VAL(eq) usb_rx_eq[eq].eq2
|
|
#define PS5169_USB_RX_EQ3_VAL(eq) usb_rx_eq[eq].eq3
|
|
|
|
/* usb rx gain */
|
|
#define PS5169_USB_RX_GAIN_REG 0x5c
|
|
#define PS5169_USB_RX_GAIN_MASK BIT(2)
|
|
#define PS5169_USB_RX_GAIN_MAX 1 /* -0.9db */
|
|
#define PS5169_USB_RX_GAIN_DEFAULT 0 /* 0db */
|
|
|
|
/* configuration */
|
|
#define PS5169_CONFIG_REG 0x40
|
|
#define PS5169_CONFIG_REG_DEFAULT 0x40
|
|
#define PS5169_PD_ACTIVE BIT(7)
|
|
#define PS5169_USB3_EN BIT(6)
|
|
#define PS5169_DP_EN BIT(5)
|
|
#define PS5169_FLIPPED BIT(4)
|
|
#define PS5169_EMODE BIT(3)
|
|
|
|
/* chip id */
|
|
#define PS5169_CHIPID_REG 0xAC
|
|
#define PS5169_CHIP_ID 0x6987
|
|
|
|
enum operation_mode {
|
|
OP_MODE_NONE, /* 4 lanes disabled */
|
|
OP_MODE_USB3, /* 2 lanes for USB and 2 lanes disabled */
|
|
OP_MODE_DP, /* 4 lanes DP */
|
|
OP_MODE_USB3_AND_DP, /* 2 lanes for USB and 2 lanes DP */
|
|
OP_MODE_DEFAULT, /* 4 lanes USB */
|
|
};
|
|
|
|
#define NOTIFIER_PRIORITY 1
|
|
|
|
struct ps5169_redriver {
|
|
struct usb_redriver r;
|
|
struct device *dev;
|
|
struct i2c_client *client;
|
|
struct regulator *vcc;
|
|
struct regulator *vio;
|
|
struct regmap *regmap;
|
|
struct reg_sequence *config_seqs;
|
|
unsigned int config_seqs_cnt;
|
|
struct dentry *debug_root;
|
|
|
|
/* tuning purpose */
|
|
unsigned int dpeq;
|
|
unsigned int dpgain;
|
|
unsigned int usbtxeq;
|
|
unsigned int usbtxgain;
|
|
unsigned int usbrxeq;
|
|
unsigned int usbrxgain;
|
|
|
|
int typec_orientation;
|
|
enum operation_mode op_mode;
|
|
int orientation_gpio;
|
|
|
|
int enable_gpio;
|
|
|
|
struct work_struct pullup_work;
|
|
bool work_ongoing;
|
|
|
|
struct work_struct host_work;
|
|
};
|
|
|
|
static const char * const opmode_string[] = {
|
|
[OP_MODE_NONE] = "NONE",
|
|
[OP_MODE_USB3] = "USB3",
|
|
[OP_MODE_DP] = "DP",
|
|
[OP_MODE_USB3_AND_DP] = "USB3 and DP",
|
|
[OP_MODE_DEFAULT] = "DEFAULT",
|
|
};
|
|
#define OPMODESTR(x) opmode_string[x]
|
|
|
|
|
|
static inline int ps5169_redriver_write_reg_bits(struct ps5169_redriver *ps5169,
|
|
unsigned int reg, unsigned int val, unsigned int mask);
|
|
static void ps5169_config_work_mode(struct ps5169_redriver *ps5169,
|
|
enum operation_mode mode);
|
|
static int ps5169_config_seqs_init(struct ps5169_redriver *ps5169);
|
|
|
|
static int ps5169_redriver_enable_chip(struct ps5169_redriver *ps5169, bool en)
|
|
{
|
|
if (!gpio_is_valid(ps5169->enable_gpio)) {
|
|
dev_err(ps5169->dev, "%s: Invalid enable_gpio: %d\n", __func__,
|
|
ps5169->enable_gpio);
|
|
return -EINVAL;
|
|
}
|
|
|
|
gpio_set_value(ps5169->enable_gpio, en);
|
|
if (en)
|
|
mdelay(10);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ps5169_get_orientation(struct usb_redriver *r)
|
|
{
|
|
struct ps5169_redriver *ps5169 =
|
|
container_of(r, struct ps5169_redriver, r);
|
|
int orientation;
|
|
|
|
dev_dbg(ps5169->dev, "%s: mode %s\n", __func__,
|
|
OPMODESTR(ps5169->op_mode));
|
|
|
|
if (!gpio_is_valid(ps5169->orientation_gpio))
|
|
orientation = 0;
|
|
else
|
|
orientation = gpio_get_value(ps5169->orientation_gpio);
|
|
|
|
dev_info(ps5169->dev, "%s: mode %s, orientation %d\n", __func__,
|
|
OPMODESTR(ps5169->op_mode), orientation);
|
|
return orientation;
|
|
}
|
|
|
|
static int ps5169_notify_connect(struct usb_redriver *r, int ort)
|
|
{
|
|
struct ps5169_redriver *ps5169 =
|
|
container_of(r, struct ps5169_redriver, r);
|
|
enum operation_mode mode = ps5169->op_mode;
|
|
|
|
if (ps5169->op_mode == OP_MODE_NONE)
|
|
mode = OP_MODE_USB3;
|
|
|
|
dev_info(ps5169->dev, "%s: op mode %s -> %s, orientation %s\n",
|
|
__func__,
|
|
OPMODESTR(ps5169->op_mode), OPMODESTR(mode),
|
|
ort == ORIENTATION_CC1 ? "CC1" : "CC2");
|
|
|
|
ps5169->op_mode = mode;
|
|
ps5169->typec_orientation = ort;
|
|
ps5169_redriver_enable_chip(ps5169, true);
|
|
ps5169_config_seqs_init(ps5169);
|
|
ps5169_config_work_mode(ps5169, ps5169->op_mode);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ps5169_notify_disconnect(struct usb_redriver *r)
|
|
{
|
|
struct ps5169_redriver *ps5169 =
|
|
container_of(r, struct ps5169_redriver, r);
|
|
|
|
if (ps5169->op_mode == OP_MODE_NONE)
|
|
return 0;
|
|
|
|
dev_info(ps5169->dev, "%s: op mode %s -> %s\n", __func__,
|
|
OPMODESTR(ps5169->op_mode), OPMODESTR(OP_MODE_NONE));
|
|
|
|
ps5169->op_mode = OP_MODE_NONE;
|
|
ps5169_redriver_enable_chip(ps5169, false);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ps5169_release_usb_lanes(struct usb_redriver *r, int ort, int num)
|
|
{
|
|
struct ps5169_redriver *ps5169 =
|
|
container_of(r, struct ps5169_redriver, r);
|
|
enum operation_mode mode = ps5169->op_mode;
|
|
|
|
if (num == 4)
|
|
mode = OP_MODE_DP;
|
|
else if (num == 2)
|
|
mode = OP_MODE_USB3_AND_DP;
|
|
else
|
|
mode = ps5169->op_mode;
|
|
|
|
ps5169->typec_orientation = ort;
|
|
if (mode != ps5169->op_mode) {
|
|
dev_info(ps5169->dev, "%s: op mode %s -> %s\n", __func__,
|
|
OPMODESTR(ps5169->op_mode), OPMODESTR(mode));
|
|
ps5169->op_mode = mode;
|
|
ps5169_redriver_enable_chip(ps5169, true);
|
|
ps5169_config_seqs_init(ps5169);
|
|
ps5169_config_work_mode(ps5169, mode);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void ps5169_gadget_pullup_work(struct work_struct *w)
|
|
{
|
|
struct ps5169_redriver *ps5169 =
|
|
container_of(w, struct ps5169_redriver, pullup_work);
|
|
|
|
ps5169_redriver_enable_chip(ps5169, false);
|
|
usleep_range(1000, 1500);
|
|
ps5169_redriver_enable_chip(ps5169, true);
|
|
ps5169_config_seqs_init(ps5169);
|
|
ps5169_config_work_mode(ps5169, ps5169->op_mode);
|
|
|
|
ps5169->work_ongoing = false;
|
|
}
|
|
|
|
static int ps5169_gadget_pullup_enter(struct usb_redriver *r, int is_on)
|
|
{
|
|
struct ps5169_redriver *ps5169 =
|
|
container_of(r, struct ps5169_redriver, r);
|
|
u64 time = 0;
|
|
|
|
dev_info(ps5169->dev, "%s: mode %s, %d, %d\n", __func__,
|
|
OPMODESTR(ps5169->op_mode), is_on, ps5169->work_ongoing);
|
|
|
|
if (ps5169->op_mode != OP_MODE_USB3)
|
|
return -EINVAL;
|
|
|
|
if (!is_on)
|
|
return 0;
|
|
|
|
while (ps5169->work_ongoing) {
|
|
udelay(1);
|
|
if (time++ > 500000) {
|
|
dev_warn(ps5169->dev, "pullup timeout\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
dev_info(ps5169->dev, "pull-up disable work took %llu us\n", time);
|
|
return 0;
|
|
}
|
|
|
|
static int ps5169_gadget_pullup_exit(struct usb_redriver *r, int is_on)
|
|
{
|
|
struct ps5169_redriver *ps5169 =
|
|
container_of(r, struct ps5169_redriver, r);
|
|
|
|
dev_info(ps5169->dev, "%s: mode %s, %d, %d\n", __func__,
|
|
OPMODESTR(ps5169->op_mode), is_on, ps5169->work_ongoing);
|
|
|
|
if (ps5169->op_mode != OP_MODE_USB3)
|
|
return -EINVAL;
|
|
|
|
if (is_on)
|
|
return 0;
|
|
|
|
ps5169->work_ongoing = true;
|
|
queue_work(system_highpri_wq, &ps5169->pullup_work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ps5169_host_work(struct work_struct *w)
|
|
{
|
|
struct ps5169_redriver *ps5169 =
|
|
container_of(w, struct ps5169_redriver, host_work);
|
|
|
|
ps5169_redriver_enable_chip(ps5169, false);
|
|
usleep_range(2000, 2500);
|
|
ps5169_redriver_enable_chip(ps5169, true);
|
|
ps5169_config_seqs_init(ps5169);
|
|
ps5169_config_work_mode(ps5169, ps5169->op_mode);
|
|
}
|
|
|
|
static int ps5169_host_powercycle(struct usb_redriver *r)
|
|
{
|
|
struct ps5169_redriver *ps5169 =
|
|
container_of(r, struct ps5169_redriver, r);
|
|
|
|
if (ps5169->op_mode != OP_MODE_USB3)
|
|
return -EINVAL;
|
|
|
|
schedule_work(&ps5169->host_work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ps5169_redriver_gpio_init(struct ps5169_redriver *ps5169)
|
|
{
|
|
struct device *dev = ps5169->dev;
|
|
int rc;
|
|
|
|
ps5169->orientation_gpio = of_get_named_gpio(dev->of_node, "redriver,orientation-gpio", 0);
|
|
if (!gpio_is_valid(ps5169->orientation_gpio)) {
|
|
dev_err(dev, "Failed to get orientation gpio\n");
|
|
return;
|
|
}
|
|
|
|
rc = devm_gpio_request(dev, ps5169->orientation_gpio, "redriver-orientation-gpio");
|
|
if (rc < 0) {
|
|
dev_err(dev, "Failed to request orientation gpio\n");
|
|
ps5169->orientation_gpio = -EINVAL;
|
|
return;
|
|
}
|
|
ps5169->r.has_orientation = true;
|
|
|
|
ps5169->enable_gpio = of_get_named_gpio(dev->of_node, "redriver,enable-gpio", 0);
|
|
if (!gpio_is_valid(ps5169->enable_gpio)) {
|
|
dev_err(dev, "Failed to get enable gpio\n");
|
|
return;
|
|
}
|
|
|
|
rc = devm_gpio_request(dev, ps5169->enable_gpio, "redriver-enable-gpio");
|
|
if (rc < 0) {
|
|
dev_err(dev, "Failed to request enable gpio\n");
|
|
ps5169->enable_gpio = -EINVAL;
|
|
return;
|
|
}
|
|
gpio_direction_output(ps5169->enable_gpio, 0);
|
|
}
|
|
|
|
static inline int ps5169_redriver_write_reg_bits(struct ps5169_redriver *ps5169,
|
|
unsigned int reg, unsigned int val, unsigned int mask)
|
|
{
|
|
int ret = regmap_update_bits(ps5169->regmap, reg, mask, val);
|
|
|
|
if (ret < 0)
|
|
dev_err(&ps5169->client->dev, "error update reg %u, ret=%d\n", reg, ret);
|
|
|
|
dev_dbg(&ps5169->client->dev, "update reg[%u]=[%x], %x\n", reg, val, mask);
|
|
return ret;
|
|
}
|
|
|
|
static int ps5169_config_dp_eq(struct ps5169_redriver *ps5169,
|
|
unsigned int eq)
|
|
{
|
|
if (eq > PS5169_DP_EQ_MAX) {
|
|
dev_err(&ps5169->client->dev, "error dp eq value %u\n", eq);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ps5169_redriver_write_reg_bits(ps5169, PS5169_DP_EQ0_REG,
|
|
PS5169_DP_EQ0_VAL(eq), PS5169_DP_EQ0_MASK);
|
|
|
|
ps5169_redriver_write_reg_bits(ps5169, PS5169_DP_EQ1_REG,
|
|
PS5169_DP_EQ1_VAL(eq), PS5169_DP_EQ1_MASK);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ps5169_config_dp_gain(struct ps5169_redriver *ps5169,
|
|
unsigned int gain)
|
|
{
|
|
if (gain > PS5169_DP_GAIN_MAX) {
|
|
dev_err(&ps5169->client->dev, "error dp gain value %u\n", gain);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ps5169_redriver_write_reg_bits(ps5169, PS5169_DP_GAIN_REG, gain,
|
|
PS5169_DP_GAIN_MASK);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ps5169_config_usb_tx_eq(struct ps5169_redriver *ps5169,
|
|
unsigned int eq)
|
|
{
|
|
if (eq > PS5169_USB_TX_EQ_MAX) {
|
|
dev_err(&ps5169->client->dev, "error usb tx eq value %u\n", eq);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ps5169_redriver_write_reg_bits(ps5169, PS5169_USB_TX_EQ0_REG,
|
|
PS5169_USB_TX_EQ0_VAL(eq), PS5169_USB_TX_EQ0_MASK);
|
|
|
|
ps5169_redriver_write_reg_bits(ps5169, PS5169_USB_TX_EQ1_REG,
|
|
PS5169_USB_TX_EQ1_VAL(eq), PS5169_USB_TX_EQ1_MASK);
|
|
|
|
ps5169_redriver_write_reg_bits(ps5169, PS5169_USB_TX_EQ2_REG,
|
|
PS5169_USB_TX_EQ2_VAL(eq), PS5169_USB_TX_EQ2_MASK);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ps5169_config_usb_tx_gain(struct ps5169_redriver *ps5169,
|
|
unsigned int gain)
|
|
{
|
|
if (gain > PS5169_USB_TX_GAIN_MAX) {
|
|
dev_err(&ps5169->client->dev,
|
|
"error usb tx gain value %u\n", gain);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ps5169_redriver_write_reg_bits(ps5169, PS5169_USB_TX_GAIN_REG, gain,
|
|
PS5169_USB_TX_GAIN_MASK);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ps5169_config_usb_rx_eq(struct ps5169_redriver *ps5169,
|
|
unsigned int eq)
|
|
{
|
|
if (eq > PS5169_USB_RX_EQ_MAX) {
|
|
dev_err(&ps5169->client->dev, "error usb rx eq value %u\n", eq);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ps5169_redriver_write_reg_bits(ps5169, PS5169_USB_RX_EQ0_REG,
|
|
PS5169_USB_RX_EQ0_VAL(eq), PS5169_USB_RX_EQ0_MASK);
|
|
|
|
ps5169_redriver_write_reg_bits(ps5169, PS5169_USB_RX_EQ1_REG,
|
|
PS5169_USB_RX_EQ1_VAL(eq), PS5169_USB_RX_EQ1_MASK);
|
|
|
|
ps5169_redriver_write_reg_bits(ps5169, PS5169_USB_RX_EQ2_REG,
|
|
PS5169_USB_RX_EQ2_VAL(eq), PS5169_USB_RX_EQ2_MASK);
|
|
|
|
ps5169_redriver_write_reg_bits(ps5169, PS5169_USB_RX_EQ3_REG,
|
|
PS5169_USB_RX_EQ3_VAL(eq), PS5169_USB_RX_EQ3_MASK);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ps5169_config_usb_rx_gain(struct ps5169_redriver *ps5169,
|
|
unsigned int gain)
|
|
{
|
|
if (gain > PS5169_USB_RX_GAIN_MAX) {
|
|
dev_err(&ps5169->client->dev,
|
|
"error usb rx gain value %u\n", gain);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ps5169_redriver_write_reg_bits(ps5169, PS5169_USB_RX_GAIN_REG, gain,
|
|
PS5169_USB_RX_GAIN_MASK);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* save user configuration and dump for device tree */
|
|
static inline void update_config_reg(struct ps5169_redriver *ps5169,
|
|
unsigned int reg, unsigned int val, unsigned int mask)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ps5169->config_seqs_cnt; i++)
|
|
if (ps5169->config_seqs[i].reg == reg) {
|
|
ps5169->config_seqs[i].def &= ~mask;
|
|
ps5169->config_seqs[i].def |= (val & mask);
|
|
break;
|
|
}
|
|
|
|
if (i == ps5169->config_seqs_cnt)
|
|
pr_err("can't find reg %u in init regs\n", reg);
|
|
}
|
|
|
|
static int dp_eq_get(void *data, u64 *val)
|
|
{
|
|
struct ps5169_redriver *ps5169 = data;
|
|
|
|
*val = ps5169->dpeq;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dp_eq_set(void *data, u64 val)
|
|
{
|
|
int ret;
|
|
|
|
struct ps5169_redriver *ps5169 = data;
|
|
|
|
ret = ps5169_config_dp_eq(ps5169, val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ps5169->dpeq = val;
|
|
|
|
update_config_reg(ps5169, PS5169_DP_EQ0_REG,
|
|
PS5169_DP_EQ0_VAL(val), PS5169_DP_EQ0_MASK);
|
|
|
|
update_config_reg(ps5169, PS5169_DP_EQ1_REG,
|
|
PS5169_DP_EQ1_VAL(val), PS5169_DP_EQ1_MASK);
|
|
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_DEBUGFS_ATTRIBUTE(dp_eq_ops, dp_eq_get, dp_eq_set, "%llu\n");
|
|
|
|
static int dp_gain_get(void *data, u64 *val)
|
|
{
|
|
struct ps5169_redriver *ps5169 = data;
|
|
|
|
*val = ps5169->dpgain;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dp_gain_set(void *data, u64 val)
|
|
{
|
|
int ret;
|
|
|
|
struct ps5169_redriver *ps5169 = data;
|
|
|
|
ret = ps5169_config_dp_gain(ps5169, val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ps5169->dpgain = val;
|
|
|
|
update_config_reg(ps5169, PS5169_DP_GAIN_REG, val,
|
|
PS5169_DP_GAIN_MASK);
|
|
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_DEBUGFS_ATTRIBUTE(dp_gain_ops, dp_gain_get, dp_gain_set, "%llu\n");
|
|
|
|
static int usb_tx_eq_get(void *data, u64 *val)
|
|
{
|
|
struct ps5169_redriver *ps5169 = data;
|
|
|
|
*val = ps5169->usbtxeq;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int usb_tx_eq_set(void *data, u64 val)
|
|
{
|
|
int ret;
|
|
|
|
struct ps5169_redriver *ps5169 = data;
|
|
|
|
ret = ps5169_config_usb_tx_eq(ps5169, val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ps5169->usbtxeq = val;
|
|
|
|
update_config_reg(ps5169, PS5169_USB_TX_EQ0_REG,
|
|
PS5169_USB_TX_EQ0_VAL(val), PS5169_USB_TX_EQ0_MASK);
|
|
|
|
update_config_reg(ps5169, PS5169_USB_TX_EQ1_REG,
|
|
PS5169_USB_TX_EQ1_VAL(val), PS5169_USB_TX_EQ1_MASK);
|
|
|
|
update_config_reg(ps5169, PS5169_USB_TX_EQ2_REG,
|
|
PS5169_USB_TX_EQ2_VAL(val), PS5169_USB_TX_EQ2_MASK);
|
|
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_DEBUGFS_ATTRIBUTE(usb_tx_eq_ops, usb_tx_eq_get, usb_tx_eq_set, "%llu\n");
|
|
|
|
static int usb_tx_gain_get(void *data, u64 *val)
|
|
{
|
|
struct ps5169_redriver *ps5169 = data;
|
|
|
|
*val = ps5169->usbtxgain;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int usb_tx_gain_set(void *data, u64 val)
|
|
{
|
|
int ret;
|
|
|
|
struct ps5169_redriver *ps5169 = data;
|
|
|
|
ret = ps5169_config_usb_tx_gain(ps5169, val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ps5169->usbtxgain = val;
|
|
|
|
update_config_reg(ps5169, PS5169_USB_TX_GAIN_REG, val,
|
|
PS5169_USB_TX_GAIN_MASK);
|
|
|
|
return ret;
|
|
}
|
|
|
|
DEFINE_DEBUGFS_ATTRIBUTE(usb_tx_gain_ops,
|
|
usb_tx_gain_get, usb_tx_gain_set, "%llu\n");
|
|
|
|
static int usb_rx_eq_get(void *data, u64 *val)
|
|
{
|
|
struct ps5169_redriver *ps5169 = data;
|
|
|
|
*val = ps5169->usbrxeq;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int usb_rx_eq_set(void *data, u64 val)
|
|
{
|
|
int ret;
|
|
|
|
struct ps5169_redriver *ps5169 = data;
|
|
|
|
ret = ps5169_config_usb_rx_eq(ps5169, val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ps5169->usbtxeq = val;
|
|
|
|
update_config_reg(ps5169, PS5169_USB_RX_EQ0_REG,
|
|
PS5169_USB_RX_EQ0_VAL(val), PS5169_USB_RX_EQ0_MASK);
|
|
|
|
update_config_reg(ps5169, PS5169_USB_RX_EQ1_REG,
|
|
PS5169_USB_RX_EQ1_VAL(val), PS5169_USB_RX_EQ1_MASK);
|
|
|
|
update_config_reg(ps5169, PS5169_USB_RX_EQ2_REG,
|
|
PS5169_USB_RX_EQ2_VAL(val), PS5169_USB_RX_EQ2_MASK);
|
|
|
|
update_config_reg(ps5169, PS5169_USB_RX_EQ3_REG,
|
|
PS5169_USB_RX_EQ3_VAL(val), PS5169_USB_RX_EQ3_MASK);
|
|
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_DEBUGFS_ATTRIBUTE(usb_rx_eq_ops,
|
|
usb_rx_eq_get, usb_rx_eq_set, "%llu\n");
|
|
|
|
static int usb_rx_gain_get(void *data, u64 *val)
|
|
{
|
|
struct ps5169_redriver *ps5169 = data;
|
|
|
|
*val = ps5169->usbrxgain;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int usb_rx_gain_set(void *data, u64 val)
|
|
{
|
|
int ret;
|
|
|
|
struct ps5169_redriver *ps5169 = data;
|
|
|
|
ret = ps5169_config_usb_rx_gain(ps5169, val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ps5169->usbrxgain = val;
|
|
|
|
update_config_reg(ps5169, PS5169_USB_RX_GAIN_REG, val,
|
|
PS5169_USB_RX_GAIN_MASK);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int config_seqs_show(struct seq_file *s, void *unused)
|
|
{
|
|
struct ps5169_redriver *ps5169 = s->private;
|
|
int i;
|
|
|
|
for (i = 0; i < ps5169->config_seqs_cnt; i++)
|
|
seq_printf(s, "0x%02x 0x%02x %u\n",
|
|
ps5169->config_seqs[i].reg,
|
|
ps5169->config_seqs[i].def,
|
|
ps5169->config_seqs[i].delay_us);
|
|
|
|
return 0;
|
|
}
|
|
DEFINE_SHOW_ATTRIBUTE(config_seqs);
|
|
|
|
DEFINE_DEBUGFS_ATTRIBUTE(usb_rx_gain_ops,
|
|
usb_rx_gain_get, usb_rx_gain_set, "%llu\n");
|
|
|
|
static void ps5169_debugfs_create(struct ps5169_redriver *ps5169)
|
|
{
|
|
struct i2c_client *client = ps5169->client;
|
|
|
|
ps5169->debug_root = debugfs_create_dir("ps5169", NULL);
|
|
if (!ps5169->debug_root) {
|
|
dev_warn(&client->dev, "couldn't create debug dir\n");
|
|
return;
|
|
}
|
|
|
|
debugfs_create_file("dp-eq", 0644,
|
|
ps5169->debug_root, ps5169, &dp_eq_ops);
|
|
|
|
debugfs_create_file("dp-gain", 0644,
|
|
ps5169->debug_root, ps5169, &dp_gain_ops);
|
|
|
|
debugfs_create_file("usb-tx-eq", 0644,
|
|
ps5169->debug_root, ps5169, &usb_tx_eq_ops);
|
|
|
|
debugfs_create_file("usb-tx-gain", 0644,
|
|
ps5169->debug_root, ps5169, &usb_tx_gain_ops);
|
|
|
|
debugfs_create_file("usb-rx-eq", 0644,
|
|
ps5169->debug_root, ps5169, &usb_rx_eq_ops);
|
|
|
|
debugfs_create_file("usb-rx-gain", 0644,
|
|
ps5169->debug_root, ps5169, &usb_rx_gain_ops);
|
|
|
|
debugfs_create_file("config_seqs", 0444,
|
|
ps5169->debug_root, ps5169, &config_seqs_fops);
|
|
}
|
|
|
|
static int ps5169_config_seqs_init(struct ps5169_redriver *ps5169)
|
|
{
|
|
int ret = 0;
|
|
|
|
regmap_register_patch(ps5169->regmap,
|
|
ps5169->config_seqs, ps5169->config_seqs_cnt);
|
|
|
|
ret = ps5169_redriver_write_reg_bits(ps5169, PS5169_CONFIG_REG,
|
|
PS5169_PD_ACTIVE, PS5169_PD_ACTIVE);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void ps5169_config_work_mode(struct ps5169_redriver *ps5169,
|
|
enum operation_mode mode)
|
|
{
|
|
struct i2c_client *client = ps5169->client;
|
|
unsigned int val, mask;
|
|
|
|
switch (mode) {
|
|
case OP_MODE_NONE:
|
|
mask = PS5169_PD_ACTIVE | PS5169_USB3_EN | PS5169_DP_EN;
|
|
val = PS5169_PD_ACTIVE;
|
|
dev_dbg(&client->dev, "write config %02x | %02x\n", val, mask);
|
|
ps5169_redriver_write_reg_bits(ps5169,
|
|
PS5169_CONFIG_REG, val, mask);
|
|
ps5169_redriver_write_reg_bits(ps5169, 0xa0, 0x02, 0xff);
|
|
ps5169_redriver_write_reg_bits(ps5169, 0xa1, 0x00, 0xff);
|
|
break;
|
|
case OP_MODE_USB3:
|
|
mask = PS5169_PD_ACTIVE | PS5169_USB3_EN | PS5169_DP_EN |
|
|
PS5169_FLIPPED;
|
|
val = PS5169_PD_ACTIVE | PS5169_USB3_EN;
|
|
if (ps5169->typec_orientation == ORIENTATION_CC2)
|
|
val |= PS5169_FLIPPED;
|
|
dev_dbg(&client->dev, "write config %02x | %02x\n", val, mask);
|
|
ps5169_redriver_write_reg_bits(ps5169,
|
|
PS5169_CONFIG_REG, val, mask);
|
|
break;
|
|
case OP_MODE_DP:
|
|
mask = PS5169_PD_ACTIVE | PS5169_USB3_EN | PS5169_DP_EN |
|
|
PS5169_FLIPPED;
|
|
val = PS5169_PD_ACTIVE | PS5169_DP_EN;
|
|
if (ps5169->typec_orientation == ORIENTATION_CC2)
|
|
val |= PS5169_FLIPPED;
|
|
dev_dbg(&client->dev, "write config %02x | %02x\n", val, mask);
|
|
ps5169_redriver_write_reg_bits(ps5169,
|
|
PS5169_CONFIG_REG, val, mask);
|
|
ps5169_redriver_write_reg_bits(ps5169, 0xa0, 0x00, 0xff);
|
|
ps5169_redriver_write_reg_bits(ps5169, 0xa1, 0x04, 0xff);
|
|
break;
|
|
case OP_MODE_USB3_AND_DP:
|
|
mask = PS5169_PD_ACTIVE | PS5169_USB3_EN | PS5169_DP_EN |
|
|
PS5169_FLIPPED;
|
|
val = PS5169_PD_ACTIVE | PS5169_USB3_EN | PS5169_DP_EN;
|
|
if (ps5169->typec_orientation == ORIENTATION_CC2)
|
|
val |= PS5169_FLIPPED;
|
|
dev_dbg(&client->dev, "write config %02x | %02x\n", val, mask);
|
|
ps5169_redriver_write_reg_bits(ps5169,
|
|
PS5169_CONFIG_REG, val, mask);
|
|
ps5169_redriver_write_reg_bits(ps5169, 0xa0, 0x00, 0xff);
|
|
ps5169_redriver_write_reg_bits(ps5169, 0xa1, 0x04, 0xff);
|
|
break;
|
|
default:
|
|
pr_err("operation mode is not support\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
static const struct regmap_config ps5169_regmap = {
|
|
.max_register = 0xff,
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
};
|
|
|
|
static int ps5169_i2c_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *dev_id)
|
|
{
|
|
struct device *dev = &client->dev;
|
|
struct ps5169_redriver *ps5169;
|
|
int ret, size = 0;
|
|
u32 device_id;
|
|
|
|
ps5169 = devm_kzalloc(dev, sizeof(*ps5169), GFP_KERNEL);
|
|
if (!ps5169)
|
|
return -ENOMEM;
|
|
|
|
ps5169->dev = dev;
|
|
ps5169->client = client;
|
|
ps5169_redriver_gpio_init(ps5169);
|
|
|
|
of_get_property(dev->of_node, "config-seq", &size);
|
|
if (!size || size % (3 * sizeof(unsigned int))) {
|
|
dev_err(dev, "no config-seq or size is wrong\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ps5169->config_seqs = devm_kzalloc(dev, size, GFP_KERNEL);
|
|
if (!ps5169->config_seqs)
|
|
return -ENOMEM;
|
|
ps5169->config_seqs_cnt = size / (3 * sizeof(unsigned int));
|
|
of_property_read_u32_array(dev->of_node,
|
|
"config-seq",
|
|
(u32 *)ps5169->config_seqs,
|
|
ps5169->config_seqs_cnt * 3);
|
|
|
|
ps5169->regmap = devm_regmap_init_i2c(client, &ps5169_regmap);
|
|
if (IS_ERR(ps5169->regmap)) {
|
|
ret = PTR_ERR(ps5169->regmap);
|
|
dev_err(dev, "failed to allocate register map: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ps5169->vcc = devm_regulator_get_optional(&client->dev, "vcc");
|
|
if (IS_ERR(ps5169->vcc) || ps5169->vcc == NULL) {
|
|
dev_err(&client->dev, "Could not get vcc power regulator\n");
|
|
} else {
|
|
ret = regulator_enable(ps5169->vcc);
|
|
if (ret)
|
|
dev_err(&client->dev, "Could not enable vcc power regulator\n");
|
|
}
|
|
ps5169->vio = devm_regulator_get_optional(&client->dev, "vio");
|
|
if (IS_ERR(ps5169->vio) || ps5169->vio == NULL) {
|
|
dev_err(&client->dev, "Could not get vio power regulator\n");
|
|
} else {
|
|
ret = regulator_enable(ps5169->vio);
|
|
if (ret)
|
|
dev_err(&client->dev, "Could not enable vio power regulator\n");
|
|
}
|
|
|
|
ps5169_redriver_enable_chip(ps5169, true);
|
|
ret = regmap_raw_read(ps5169->regmap, PS5169_CHIPID_REG, &device_id, 2);
|
|
if (ret != 0) {
|
|
dev_err(dev, "%s,device id read failed:%d\n", __func__, ret);
|
|
} else if (device_id != PS5169_CHIP_ID) {
|
|
dev_err(dev, "%s,device id unknown: 0x%x\n", __func__, device_id);
|
|
}
|
|
dev_info(dev, "%s,device_id=0x%x\n", __func__, device_id);
|
|
|
|
i2c_set_clientdata(client, ps5169);
|
|
|
|
ps5169_config_seqs_init(ps5169);
|
|
|
|
/* how about USB enumerated before this driver load */
|
|
ps5169_config_work_mode(ps5169, OP_MODE_NONE);
|
|
ps5169->op_mode = OP_MODE_NONE;
|
|
ps5169_redriver_enable_chip(ps5169, false);
|
|
|
|
ps5169_debugfs_create(ps5169);
|
|
INIT_WORK(&ps5169->pullup_work, ps5169_gadget_pullup_work);
|
|
INIT_WORK(&ps5169->host_work, ps5169_host_work);
|
|
|
|
ps5169->r.of_node = ps5169->dev->of_node;
|
|
ps5169->r.release_usb_lanes = ps5169_release_usb_lanes;
|
|
ps5169->r.notify_connect = ps5169_notify_connect;
|
|
ps5169->r.notify_disconnect = ps5169_notify_disconnect;
|
|
ps5169->r.get_orientation = ps5169_get_orientation;
|
|
ps5169->r.gadget_pullup_enter = ps5169_gadget_pullup_enter;
|
|
ps5169->r.gadget_pullup_exit = ps5169_gadget_pullup_exit;
|
|
ps5169->r.host_powercycle = ps5169_host_powercycle;
|
|
usb_add_redriver(&ps5169->r);
|
|
|
|
dev_info(dev, "%s,ps5169 probe done.\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ps5169_i2c_remove(struct i2c_client *client)
|
|
{
|
|
struct ps5169_redriver *ps5169 = i2c_get_clientdata(client);
|
|
|
|
if (usb_remove_redriver(&ps5169->r))
|
|
return -EINVAL;
|
|
|
|
debugfs_remove(ps5169->debug_root);
|
|
if (!IS_ERR_OR_NULL(ps5169->vcc)) {
|
|
if (regulator_is_enabled(ps5169->vcc))
|
|
regulator_disable(ps5169->vcc);
|
|
devm_regulator_put(ps5169->vcc);
|
|
}
|
|
if (!IS_ERR_OR_NULL(ps5169->vio)) {
|
|
if (regulator_is_enabled(ps5169->vio))
|
|
regulator_disable(ps5169->vio);
|
|
devm_regulator_put(ps5169->vio);
|
|
}
|
|
|
|
if (gpio_is_valid(ps5169->enable_gpio)) {
|
|
devm_gpio_free(ps5169->dev, ps5169->enable_gpio);
|
|
ps5169->enable_gpio = -EINVAL;
|
|
}
|
|
|
|
if (gpio_is_valid(ps5169->orientation_gpio)) {
|
|
devm_gpio_free(ps5169->dev, ps5169->orientation_gpio);
|
|
ps5169->orientation_gpio = -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused redriver_i2c_suspend(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct ps5169_redriver *ps5169 = i2c_get_clientdata(client);
|
|
|
|
dev_dbg(ps5169->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 (ps5169->op_mode == OP_MODE_DP ||
|
|
ps5169->op_mode == OP_MODE_NONE ||
|
|
ps5169->op_mode == OP_MODE_DEFAULT)
|
|
return 0;
|
|
|
|
ps5169_redriver_enable_chip(ps5169, false);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused redriver_i2c_resume(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct ps5169_redriver *ps5169 = i2c_get_clientdata(client);
|
|
|
|
dev_dbg(ps5169->dev, "%s: SS USB redriver resume.\n",
|
|
__func__);
|
|
|
|
/* no suspend happen in following mode */
|
|
if (ps5169->op_mode == OP_MODE_DP ||
|
|
ps5169->op_mode == OP_MODE_NONE ||
|
|
ps5169->op_mode == OP_MODE_DEFAULT)
|
|
return 0;
|
|
|
|
ps5169_redriver_enable_chip(ps5169, true);
|
|
ps5169_config_seqs_init(ps5169);
|
|
ps5169_config_work_mode(ps5169, ps5169->op_mode);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static SIMPLE_DEV_PM_OPS(redriver_i2c_pm, redriver_i2c_suspend,
|
|
redriver_i2c_resume);
|
|
|
|
static void ps5169_i2c_shutdown(struct i2c_client *client)
|
|
{
|
|
struct ps5169_redriver *ps5169 = i2c_get_clientdata(client);
|
|
int ret;
|
|
|
|
ret = ps5169_redriver_enable_chip(ps5169, false);
|
|
if (ret < 0)
|
|
dev_err(&client->dev,
|
|
"%s: fail to disable redriver.\n",
|
|
__func__);
|
|
else
|
|
dev_dbg(&client->dev,
|
|
"%s: successfully disable redriver.\n",
|
|
__func__);
|
|
}
|
|
|
|
static const struct of_device_id ps5169_match_table[] = {
|
|
{ .compatible = "parade,ps5169-redriver",},
|
|
{ },
|
|
};
|
|
|
|
static const struct i2c_device_id ps5169_i2c_id[] = {
|
|
{ "ps5169 redriver", 0 },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, ps5169_i2c_id);
|
|
|
|
static struct i2c_driver ps5169_i2c_driver = {
|
|
.driver = {
|
|
.name = "ps5169 redriver",
|
|
.of_match_table = ps5169_match_table,
|
|
.pm = &redriver_i2c_pm,
|
|
},
|
|
|
|
.probe = ps5169_i2c_probe,
|
|
.remove = ps5169_i2c_remove,
|
|
|
|
.shutdown = ps5169_i2c_shutdown,
|
|
|
|
.id_table = ps5169_i2c_id,
|
|
};
|
|
module_i2c_driver(ps5169_i2c_driver);
|
|
|
|
MODULE_DESCRIPTION("Parade PS5169 USB3.1 Gen2 Type-C 10Gbps Linear Redriver");
|
|
MODULE_LICENSE("GPL v2");
|