// 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");
