/*
 * SiI8620 Linux Driver
 *
 * Copyright (C) 2013-2014 Silicon Image, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation version 2.
 * This program is distributed AS-IS WITHOUT ANY WARRANTY of any
 * kind, whether express or implied; INCLUDING without the implied warranty
 * of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE or NON-INFRINGEMENT.
 * See the GNU General Public License for more details at
 * http://www.gnu.org/licenses/gpl-2.0.html.
 */

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>

#include <linux/types.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/semaphore.h>
#include <linux/cdev.h>
#include <linux/spi/spi.h>
#include <linux/regulator/consumer.h>

#include "si_fw_macros.h"
#include "si_infoframe.h"
#include "si_edid.h"
#include "si_mhl_defs.h"
#include "si_mhl2_edid_3d_api.h"
#include "si_mhl_tx_hw_drv_api.h"
#ifdef MEDIA_DATA_TUNNEL_SUPPORT
#include <linux/input.h>
#include "si_mdt_inputdev.h"
#endif
#include "mhl_linux_tx.h"
#include "mhl_supp.h"
#include "platform.h"
#include "si_mhl_callback_api.h"
#include "si_8620_drv.h"
#include "si_8620_regs.h"

#ifdef SIMG_USE_DTS
#include <linux/of_gpio.h>
#endif


/*
 * Platform resources (I2C port, GPIOs, ...) needed to control the
 * MHL starter kit.  These default values are used to interface with
 * PandaBoard which does not (currently) use device tree.  Device tree
 * capable systems should modify the device tree to declare the platform
 * resources assigned to the MHL starter kit.  If compiled with device tree
 * support (-DSIMG_USE_DTS), this driver will override these default
 * values with those from the device tree.
 */
#define GPIO_MHL_INT			138	/* W_INT */
#define GPIO_BB_RESET			140	/* P_BB_RST# */
#define I2C_ADAPTER				4

#define GPIO_EXP_ADDR				0x40

#define RESET_PULSE_WIDTH			1	/* In ms	*/

/*
 * NOTE: The following GPIO expander register type address
 * offsets are all defined with the address auto-increment
 * bit set (0x80)
 */
#define GPIO_EXP_INPUT_REGS_OFFSET		0x80
#define GPIO_EXP_OUTPUT_REGS_OFFSET		0x88
#define GPIO_EXP_POL_INVERT_REGS_OFFSET		0x90
#define GPIO_EXP_IO_CONFIG_REGS_OFFSET		0x98
#define GPIO_EXP_INTR_MASK_REGS_OFFSET		0xA0

#define GPIO_EXP_BANK_2_OUTPUT_DEFAULT		0xFF
#define GPIO_EXP_BANK_2_3D			(0x01 << 0)
#define GPIO_EXP_BANK_2_PKD_PXL			(0x01 << 1)
#define GPIO_EXP_BANK_2_HDCP_ON			(0x01 << 2)
#define GPIO_EXP_BANK_2_N_TCODE			(0x01 << 3)
#define GPIO_EXP_BANK_2_LED_USB_MODE		(0x01 << 4)
#define GPIO_EXP_BANK_2_SPR_LED2		(0x01 << 5)
#define GPIO_EXP_BANK_2_SPR_LED3		(0x01 << 6)
#define GPIO_EXP_BANK_2_SPR_LED4		(0x01 << 7)

#define GPIO_EXP_BANK_3_OUTPUT_DEFAULT		0x67

#define GPIO_EXP_BANK_3_MHL_TX_RST_B		(0x01 << 0)
#define GPIO_EXP_BANK_3_FW_WAKE_A		(0x01 << 1)
#define GPIO_EXP_BANK_3_CHG_DET			(0x01 << 2)
#define GPIO_EXP_BANK_3_XO3_SINK_VBUS_SENSE	(0x01 << 3)
#define GPIO_EXP_BANK_3_12V_PS_SENSE		(0x01 << 4)
#define GPIO_EXP_BANK_3_EEPROM_WR_EN		(0x01 << 5)
#define GPIO_EXP_BANK_3_TX2MHLRX_PWR_A		(0x01 << 6)
#define GPIO_EXP_BANK_3_M2U_VBUS_CTRL_A		(0x01 << 7)

#define GPIO_EXP_BANK_4_OUTPUT_DEFAULT		0xF0
#define GPIO_EXP_BANK_4_DSW9			(0x01 << 0)
#define GPIO_EXP_BANK_4_DSW10			(0x01 << 1)
#define GPIO_EXP_BANK_4_DSW11			(0x01 << 2)
#define GPIO_EXP_BANK_4_DSW12			(0x01 << 3)
#define GPIO_EXP_BANK_4_USB_SW_CTRL0		(0x01 << 4)
#define GPIO_EXP_BANK_4_USB_SW_CTRL1		(0x01 << 5)
#define GPIO_EXP_BANK_4_LED15_AMBER		(0x01 << 6)
#define GPIO_EXP_BANK_4_LED15_GREEN		(0x01 << 7)

#define GET_FROM_MODULE_PARAM			-1
#define GPIO_ON_EXPANDER			-2

#define REG_PCA_950x_PORT_0_INPUT		0x00
#define REG_PCA_950x_PORT_1_INPUT		0x01
#define REG_PCA_950x_PORT_2_INPUT		0x02
#define REG_PCA_950x_PORT_3_INPUT		0x03
#define REG_PCA_950x_PORT_4_INPUT		0x04

#define REG_PCA_950x_PORT_0_OUTPUT		0x08
#define REG_PCA_950x_PORT_1_OUTPUT		0x09
#define REG_PCA_950x_PORT_2_OUTPUT		0x0A
#define REG_PCA_950x_PORT_3_OUTPUT		0x0B
#define REG_PCA_950x_PORT_4_OUTPUT		0x0C

u8 gpio_exp_bank2_output;
u8 gpio_exp_bank3_output;
u8 gpio_exp_bank4_output;

static char *buildTime = "Built " __DATE__ "-" __TIME__;
static char *buildVersion = "1.03." BUILD_NUM_STRING;

struct semaphore platform_lock;
static uint32_t platform_flags;
bool probe_fail;

static struct spi_device *spi_dev;
#define SPI_BUS_NUM		1
#define	SPI_CHIP_SEL		0
#define SPI_TRANSFER_MODE	SPI_MODE_0
#define SPI_BUS_SPEED		1000000

enum si_spi_opcodes {
	spi_op_disable = 0x04,
	spi_op_enable = 0x06,
	spi_op_clear_status = 0x07,
	spi_op_reg_read = 0x60,
	spi_op_reg_write = 0x61,
	spi_op_ddc_reg_read = 0x62,
	spi_op_emsc_read = 0x80,
	spi_op_emsc_write = 0x81,
	spi_op_slow_cbus_read = 0x90,
	spi_op_slow_cbus_write = 0x91
};

#define MAX_SPI_PAYLOAD_SIZE		LOCAL_BLK_RCV_BUFFER_SIZE
#define MAX_SPI_CMD_SIZE		3
#define EMSC_WRITE_SPI_CMD_SIZE		1
#define EMSC_READ_SPI_CMD_SIZE		1
#define MAX_SPI_DUMMY_XFER_BYTES	20
#define MAX_SPI_XFER_BUFFER_SIZE	(MAX_SPI_CMD_SIZE + \
		MAX_SPI_DUMMY_XFER_BYTES + MAX_SPI_PAYLOAD_SIZE)
#define MAX_SPI_EMSC_BLOCK_SIZE (MAX_SPI_CMD_SIZE + MAX_SPI_PAYLOAD_SIZE)

#define MAX_I2C_PAYLOAD_SIZE		LOCAL_BLK_RCV_BUFFER_SIZE
#define MAX_I2C_CMD_SIZE		0

#define MAX_I2C_EMSC_BLOCK_SIZE (MAX_I2C_CMD_SIZE + MAX_I2C_PAYLOAD_SIZE)

struct spi_xfer_mem {
	u8 *tx_buf;
	u8 *rx_buf;
	/* block commands are asynchronous to normal cbus traffic
	   and CANNOT share a buffer.
	 */
	uint8_t *block_tx_buffers;
	struct spi_transfer spi_xfer[2];
	struct spi_message spi_cmd;
} spi_mem;

struct i2c_xfer_mem {
	uint8_t *block_tx_buffers;
} i2c_mem;

#ifdef MHL_GPIO_EXPANDER
static bool expander_enabled = true;
#else
static bool expander_enabled;
#endif

static int gpio_expander_transfer(u8 offset, u16 count, u8 *values, bool write);

#if defined(SIMG_USE_DTS)
static u32 i2c_adapter_num = I2C_ADAPTER;
#endif
static u32 spi_bus_num = SPI_BUS_NUM;
static struct i2c_adapter *i2c_bus_adapter;

struct i2c_dev_info {
	uint8_t dev_addr;
	struct i2c_client *client;
};

#define I2C_DEV_INFO(addr) \
	{.dev_addr = addr >> 1, .client = NULL}

static struct i2c_dev_info device_addresses[] = {
	I2C_DEV_INFO(SA_TX_PAGE_0),
	I2C_DEV_INFO(SA_TX_PAGE_1),
	I2C_DEV_INFO(SA_TX_PAGE_2),
	I2C_DEV_INFO(SA_TX_PAGE_3),
	I2C_DEV_INFO(SA_TX_PAGE_6),
	I2C_DEV_INFO(SA_TX_CBUS),
	I2C_DEV_INFO(GPIO_EXP_ADDR),
};

int debug_level;
bool debug_reg_dump;
bool input_dev_rap = 1;
bool input_dev_rcp = 1;
bool input_dev_ucp = 1;
#if (INCLUDE_RBP == 1)
bool input_dev_rbp = 1;
#endif
int hdcp_content_type;
bool use_spi = 0;		 /* Default to i2c (0). */
int crystal_khz = 19200; /* SiI8620 SK has 19.2MHz crystal */
int use_heartbeat;

bool wait_for_user_intr;
int tmds_link_speed;
#ifdef FORCE_OCBUS_FOR_ECTS
bool force_ocbus_for_ects;
#endif
int gpio_index = 138;

module_param(debug_reg_dump, bool, S_IRUGO);
module_param(debug_level, int, S_IRUGO);

module_param(input_dev_rap, bool, S_IRUGO);
module_param(input_dev_rcp, bool, S_IRUGO);
module_param(input_dev_ucp, bool, S_IRUGO);
#if (INCLUDE_RBP == 1)
module_param(input_dev_rbp, bool, S_IRUGO);
#endif
module_param(hdcp_content_type, int, S_IRUGO);
module_param(use_spi, bool, S_IRUGO);
module_param(crystal_khz, int, S_IRUGO);
module_param(use_heartbeat, int, S_IRUGO);
module_param(wait_for_user_intr, bool, S_IRUGO);
module_param(tmds_link_speed, int, S_IRUGO);
#ifdef	FORCE_OCBUS_FOR_ECTS
module_param(force_ocbus_for_ects, bool, S_IRUGO);
#endif

module_param_named(debug_msgs, debug_level, int, S_IRUGO);

struct platform_signals_list platform_signals[] = {
	{.name = "TX_HW_RESET",
	 .gpio_number = GPIO_ON_EXPANDER,
	 .gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_3_OUTPUT},
	 .gpio_mask_PCA950x = GPIO_EXP_BANK_3_MHL_TX_RST_B,
	 .gpio_bank_value = &gpio_exp_bank3_output,
	 .param = NULL},
	{.name = "TX_FW_WAKE",
	 .gpio_number = GPIO_ON_EXPANDER,
	 .gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_3_OUTPUT},
	 .gpio_mask_PCA950x = GPIO_EXP_BANK_3_FW_WAKE_A,
	 .gpio_bank_value = &gpio_exp_bank3_output,
	 .param = NULL},
	{.name = "CHG_DET",
	 .gpio_number = GPIO_ON_EXPANDER,
	 .gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_3_OUTPUT},
	 .gpio_mask_PCA950x = GPIO_EXP_BANK_3_CHG_DET,
	 .gpio_bank_value = &gpio_exp_bank3_output,
	 .param = NULL},
	{.name = "XO3_SINK_VBUS_SENSE",
	 .gpio_number = GPIO_ON_EXPANDER,
	 .gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_3_INPUT},
	 .gpio_mask_PCA950x = GPIO_EXP_BANK_3_XO3_SINK_VBUS_SENSE,
	 .gpio_bank_value = &gpio_exp_bank3_output,
	 .param = NULL},
	{.name = "TWELVE_VOLT_PS_SENSE",
	 .gpio_number = GPIO_ON_EXPANDER,
	 .gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_3_OUTPUT},
	 .gpio_mask_PCA950x = GPIO_EXP_BANK_3_12V_PS_SENSE,
	 .gpio_bank_value = &gpio_exp_bank3_output,
	 .param = NULL},
	{.name = "EEPROM_WR_EN",
	 .gpio_number = GPIO_ON_EXPANDER,
	 .gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_3_OUTPUT},
	 .gpio_mask_PCA950x = GPIO_EXP_BANK_3_EEPROM_WR_EN,
	 .gpio_bank_value = &gpio_exp_bank3_output,
	 .param = NULL},
	{.name = "TX2MHLRX_PWR",
	 .gpio_number = GPIO_ON_EXPANDER,
	 .gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_3_OUTPUT},
	 .gpio_mask_PCA950x = GPIO_EXP_BANK_3_TX2MHLRX_PWR_A,
	 .gpio_bank_value = &gpio_exp_bank3_output,
	 .param = NULL},
	{.name = "M2U_VBUS_CTRL",
	 .gpio_number = GPIO_ON_EXPANDER,
	 .gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_3_OUTPUT},
	 .gpio_mask_PCA950x = GPIO_EXP_BANK_3_M2U_VBUS_CTRL_A,
	 .gpio_bank_value = &gpio_exp_bank3_output,
	 .param = NULL},
	{.name = "LED_3D",
	 .gpio_number = GPIO_ON_EXPANDER,
	 .gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_2_OUTPUT},
	 .gpio_mask_PCA950x = GPIO_EXP_BANK_2_3D,
	 .gpio_bank_value = &gpio_exp_bank2_output,
	 .param = NULL},
	{.name = "LED_PACKED_PIXEL",
	 .gpio_number = GPIO_ON_EXPANDER,
	 .gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_2_OUTPUT},
	 .gpio_mask_PCA950x = GPIO_EXP_BANK_2_PKD_PXL,
	 .gpio_bank_value = &gpio_exp_bank2_output,
	 .param = NULL},
	{.name = "LED_HDCP",
	 .gpio_number = GPIO_ON_EXPANDER,
	 .gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_2_OUTPUT},
	 .gpio_mask_PCA950x = GPIO_EXP_BANK_2_HDCP_ON,
	 .gpio_bank_value = &gpio_exp_bank2_output,
	 .param = NULL},
	{.name = "LED_USB_MODE",
	 .gpio_number = GPIO_ON_EXPANDER,
	 .gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_2_OUTPUT},
	 .gpio_mask_PCA950x = GPIO_EXP_BANK_2_LED_USB_MODE,
	 .gpio_bank_value = &gpio_exp_bank2_output,
	 .param = NULL},
	{.name = "LED_SPARE_2",
	 .gpio_number = GPIO_ON_EXPANDER,
	 .gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_2_OUTPUT},
	 .gpio_mask_PCA950x = GPIO_EXP_BANK_2_SPR_LED2,
	 .gpio_bank_value = &gpio_exp_bank2_output,
	 .param = NULL},
	{.name = "LED_SPARE_3",
	 .gpio_number = GPIO_ON_EXPANDER,
	 .gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_2_OUTPUT},
	 .gpio_mask_PCA950x = GPIO_EXP_BANK_2_SPR_LED3,
	 .gpio_bank_value = &gpio_exp_bank2_output,
	 .param = NULL},
	{.name = "LED_SPARE_4",
	 .gpio_number = GPIO_ON_EXPANDER,
	 .gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_2_OUTPUT},
	 .gpio_mask_PCA950x = GPIO_EXP_BANK_2_SPR_LED4,
	 .gpio_bank_value = &gpio_exp_bank2_output,
	 .param = NULL},
	{.name = "X02_USB_SW_CTRL",
	 .gpio_number = GPIO_ON_EXPANDER,
	 .gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_4_OUTPUT},
	 .gpio_mask_PCA950x = (GPIO_EXP_BANK_4_USB_SW_CTRL0 |
			       GPIO_EXP_BANK_4_USB_SW_CTRL1),
	 .gpio_bank_value = &gpio_exp_bank4_output,
	 .param = NULL},
	{.name = "X02_USB_SW_CTRL0",
	 .gpio_number = GPIO_ON_EXPANDER,
	 .gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_4_OUTPUT},
	 .gpio_mask_PCA950x = (GPIO_EXP_BANK_4_USB_SW_CTRL0),
	 .gpio_bank_value = &gpio_exp_bank4_output,
	 .param = NULL},
	{.name = "X02_USB_SW_CTRL1",
	 .gpio_number = GPIO_ON_EXPANDER,
	 .gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_4_OUTPUT},
	 .gpio_mask_PCA950x = (GPIO_EXP_BANK_4_USB_SW_CTRL1),
	 .gpio_bank_value = &gpio_exp_bank4_output,
	 .param = NULL},
	{.name = "X02_LED15_AMBER",
	 .gpio_number = GPIO_ON_EXPANDER,
	 .gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_4_OUTPUT},
	 .gpio_mask_PCA950x = (GPIO_EXP_BANK_4_LED15_AMBER),
	 .gpio_bank_value = &gpio_exp_bank4_output,
	 .param = NULL},
	{.name = "X02_LED15_GREEN",
	 .gpio_number = GPIO_ON_EXPANDER,
	 .gpio_reg_PCA950x = {GPIO_EXP_ADDR, REG_PCA_950x_PORT_4_OUTPUT},
	 .gpio_mask_PCA950x = (GPIO_EXP_BANK_4_LED15_GREEN),
	 .gpio_bank_value = &gpio_exp_bank4_output,
	 .param = NULL}
};

#define MHL_INT_INDEX		0
#define MHL_RESET_INDEX		1
static struct gpio starter_kit_control_gpios[] = {
	/*
	 * GPIO signals needed for the starter kit board.
	 */
	{GPIO_MHL_INT, GPIOF_IN, "MHL_intr"},
	{GPIO_BB_RESET, GPIOF_OUT_INIT_HIGH, "MHL_reset"},
};

static inline int platform_read_i2c_block(struct i2c_adapter *i2c_bus, u8 page,
	u8 offset, u16 count, u8 *values)
{
	struct i2c_msg msg[2];

	msg[0].flags = 0;
	msg[0].addr = page >> 1;
	msg[0].buf = &offset;
	msg[0].len = 1;

	msg[1].flags = I2C_M_RD;
	msg[1].addr = page >> 1;
	msg[1].buf = values;
	msg[1].len = count;

	return i2c_transfer(i2c_bus, msg, 2);
}

static inline int platform_write_i2c_block(struct i2c_adapter *i2c_bus, u8 page,
	u8 offset, u16 count, u8 *values)
{
	struct i2c_msg msg;
	u8 *buffer;
	int ret;

	buffer = kmalloc(count + 1, GFP_KERNEL);
	if (!buffer) {
		printk(KERN_ERR "%s:%d buffer allocation failed\n",
			__func__, __LINE__);
		return -ENOMEM;
	}

	buffer[0] = offset;
	memmove(&buffer[1], values, count);

	msg.flags = 0;
	msg.addr = page >> 1;
	msg.buf = buffer;
	msg.len = count + 1;

	ret = i2c_transfer(i2c_bus, &msg, 1);

	kfree(buffer);

	if (ret != 1) {
		printk(KERN_ERR "%s:%d I2c write failed 0x%02x:0x%02x\n",
			__func__, __LINE__, page, offset);
		ret = -EIO;
	} else {
		ret = 0;
	}

	return ret;
}

static int platform_read_i2c_reg(struct i2c_adapter *bus_adapter_i2c, u8 page,
	u8 offset)
{
	int ret;
	u8 byte_read;

	ret = platform_read_i2c_block(bus_adapter_i2c, page, offset, 1,
		&byte_read);
	MHL_TX_DBG_INFO("\tGI2C_R %2x:%2x = %2x\n", page, offset, ret);
	if (ret != 2) {
		printk(KERN_ERR "%s:%d I2c read failed, 0x%02x:0x%02x\n",
			__func__, __LINE__, page, offset);
		ret = -EIO;
	} else {
		ret = 0;
	}
	return ret ? ret : byte_read;
}

static int platform_write_i2c_reg(struct i2c_adapter *bus_adapter_i2c, u8 page,
	u8 offset, u8 value)
{
	MHL_TX_DBG_INFO("\tGI2C_W %2x:%2x <- %2x\n", page, offset, value);
	return platform_write_i2c_block(bus_adapter_i2c, page, offset, 1,
					&value);
}

uint32_t platform_get_flags(void)
{
	return platform_flags;
}

static void toggle_reset_n(void)
{
	MHL_TX_DBG_INFO("Toggle MHL_RST_B/RESET_N pin. Resets 8620 only.\n");
	gpio_exp_bank3_output &= ~GPIO_EXP_BANK_3_MHL_TX_RST_B;
	gpio_expander_transfer(GPIO_EXP_OUTPUT_REGS_OFFSET + 3,
			       1, &gpio_exp_bank3_output, true);

	/* Without this, we see a 500ns reset pulse. Enforce 1ms.*/
	msleep(RESET_PULSE_WIDTH);

	gpio_exp_bank3_output |= GPIO_EXP_BANK_3_MHL_TX_RST_B;
	gpio_expander_transfer(GPIO_EXP_OUTPUT_REGS_OFFSET + 3,
			       1, &gpio_exp_bank3_output, true);

}

static void toggle_BB_RST(int reset_period)
{
	MHL_TX_DBG_INFO("Toggle BB_RST# pin. Resets GPIO expander AND 8620\n");
	gpio_set_value(starter_kit_control_gpios[MHL_RESET_INDEX].gpio, 0);
	msleep(reset_period);
	gpio_set_value(starter_kit_control_gpios[MHL_RESET_INDEX].gpio, 1);

}

int is_interrupt_asserted(void)
{
	return gpio_get_value(starter_kit_control_gpios[MHL_INT_INDEX].gpio)
			? 0 : 1;
}

int get_config(void *dev_context, int config_idx)
{
	int pin_state = 0;

	if (config_idx < ARRAY_SIZE(platform_signals)) {
		switch (platform_signals[config_idx].gpio_number) {
		case GET_FROM_MODULE_PARAM:
			pin_state = *(platform_signals[config_idx].param);
			break;
		case GPIO_ON_EXPANDER:
			pin_state =
			    (platform_read_i2c_reg
			     (i2c_bus_adapter,
			      platform_signals[config_idx].gpio_reg_PCA950x.
			      slave_addr,
			      platform_signals[config_idx].gpio_reg_PCA950x.
			      offset)
			     & platform_signals[config_idx].
			     gpio_mask_PCA950x) ? 1 : 0;
			break;
		default:
			pin_state =
				gpio_get_value(platform_signals[config_idx].
				gpio_number);
			break;
		}
	}
	return pin_state;
}

void set_pin_impl(int pin_idx, int value,
		  const char *function_name, int line_num)
{
	uint8_t bank_value;

	if (pin_idx < ARRAY_SIZE(platform_signals)) {

		MHL_TX_DBG_INFO("set_pin(%s,%d)\n",
			platform_signals[pin_idx].name, value);
		switch (platform_signals[pin_idx].gpio_number) {
		case GET_FROM_MODULE_PARAM:
			break;
		case GPIO_ON_EXPANDER:
			if (expander_enabled) {
				bank_value = *(platform_signals[pin_idx]
							.gpio_bank_value);
				if (value)
					bank_value |= platform_signals[pin_idx].
						gpio_mask_PCA950x;
				else
					bank_value &=
						~platform_signals[pin_idx].
							gpio_mask_PCA950x;

				*(platform_signals[pin_idx].gpio_bank_value) =
					bank_value;
				platform_write_i2c_reg(i2c_bus_adapter,
					       platform_signals[pin_idx].
					       gpio_reg_PCA950x.slave_addr,
					       platform_signals[pin_idx].
					       gpio_reg_PCA950x.offset,
					       bank_value);
			} else {
				bank_value = 0;
			}
			break;
		default:
			gpio_set_value(platform_signals[pin_idx].gpio_number,
				value);
			break;
		}
	}
}

void platform_mhl_tx_hw_reset(uint32_t reset_period, uint32_t reset_delay)
{
	/* then reset the chip */
	if (expander_enabled)
		toggle_reset_n();
	else
		toggle_BB_RST(100);

	if (reset_delay)
		msleep(reset_delay);

	if (use_spi) {
		u8 cmd = spi_op_enable;
		spi_write(spi_dev, &cmd, 1);
	}
}

void mhl_tx_vbus_control(enum vbus_power_state power_state)
{
	struct device *parent_dev;
	struct mhl_dev_context *dev_context;

	if (use_spi)
		parent_dev = &spi_dev->dev;
	else
		parent_dev = &device_addresses[0].client->dev;

	dev_context = dev_get_drvdata(parent_dev);

	switch (power_state) {
	case VBUS_OFF:
		set_pin(M2U_VBUS_CTRL, 0);
		set_pin(TX2MHLRX_PWR, 1);
		break;

	case VBUS_ON:
		set_pin(TX2MHLRX_PWR, 0);
		set_pin(M2U_VBUS_CTRL, 1);
		break;

	default:
		dev_err(dev_context->mhl_dev,
			"%s: Invalid power state %d received!\n",
			__func__, power_state);
		break;
	}
}

void mhl_tx_vbus_current_ctl(uint16_t max_current_in_milliamps)
{
	/*
		Starter kit does not have a PMIC.
		Implement VBUS input current limit control here.
	*/

}

int si_device_dbg_i2c_reg_xfer(void *dev_context, u8 page, u8 offset,
			       u16 count, bool rw_flag, u8 *buffer)
{
	u16 address = (page << 8) | offset;

	if (rw_flag == DEBUG_I2C_WRITE)
		return mhl_tx_write_reg_block(dev_context, address, count,
					      buffer);
	else
		return mhl_tx_read_reg_block(dev_context, address, count,
					     buffer);
}

#define MAX_DEBUG_MSG_SIZE	1024

#if defined(DEBUG)

/*
 * Return a pointer to the file name part of the
 * passed path spec string.
 */
char *find_file_name(const char *path_spec)
{
	char *pc;

	for (pc = (char *)&path_spec[strlen(path_spec)];
		pc != path_spec; --pc) {
		if ('\\' == *pc) {
			++pc;
			break;
		}
		if ('/' == *pc) {
			++pc;
			break;
		}
	}
	return pc;
}

void print_formatted_debug_msg(char *file_spec, const char *func_name,
			       int line_num, char *fmt, ...)
{
	uint8_t *msg = NULL;
	uint8_t *msg_offset;
	char *file_spec_sep = NULL;
	int remaining_msg_len = MAX_DEBUG_MSG_SIZE;
	int len;
	va_list ap;

	if (fmt == NULL)
		return;

	msg = kmalloc(remaining_msg_len, GFP_KERNEL);
	if (msg == NULL)
		return;

	msg_offset = msg;

	len = scnprintf(msg_offset, remaining_msg_len, "mhl: ");
	msg_offset += len;
	remaining_msg_len -= len;

	/* Only print the file name, not the path */
	if (file_spec != NULL)
		file_spec = find_file_name(file_spec);

	if (file_spec != NULL) {
		if (func_name != NULL)
			file_spec_sep = "->";
		else if (line_num != -1)
			file_spec_sep = ":";
	}

	if (file_spec) {
		len = scnprintf(msg_offset, remaining_msg_len, "%s", file_spec);
		msg_offset += len;
		remaining_msg_len -= len;
	}

	if (file_spec_sep) {
		len =
		    scnprintf(msg_offset, remaining_msg_len, "%s",
			      file_spec_sep);
		msg_offset += len;
		remaining_msg_len -= len;
	}

	if (func_name) {
		len = scnprintf(msg_offset, remaining_msg_len, "%s", func_name);
		msg_offset += len;
		remaining_msg_len -= len;
	}

	if (line_num != -1) {
		if ((file_spec != NULL) || (func_name != NULL))
			len =
			    scnprintf(msg_offset, remaining_msg_len, ":%d ",
				      line_num);
		else
			len =
			    scnprintf(msg_offset, remaining_msg_len, "%d ",
				      line_num);

		msg_offset += len;
		remaining_msg_len -= len;
	}

	va_start(ap, fmt);
	len = vscnprintf(msg_offset, remaining_msg_len, fmt, ap);
	va_end(ap);

	printk(msg);

	kfree(msg);
}

void dump_transfer(enum tx_interface_types if_type,
		   u8 page, u8 offset, u16 count, u8 *values, bool write)
{
	if (debug_reg_dump != 0) {
		int buf_size = 64;
		u16 idx;
		int buf_offset;
		char *buf;
		char *if_type_msg;

		switch (if_type) {
		case TX_INTERFACE_TYPE_I2C:
			if_type_msg = "I2C";
			break;
		case TX_INTERFACE_TYPE_SPI:
			if_type_msg = "SPI";
			break;
		default:
			return;
		};

		if (count > 1) {
			/* 3 chars per byte displayed */
			buf_size += count * 3;
			/* plus per display row overhead */
			buf_size += ((count / 16) + 1) * 8;
		}

		buf = kmalloc(buf_size, GFP_KERNEL);
		if (!buf)
			return;

		if (count == 1) {

			scnprintf(buf, buf_size, "   %s %02X.%02X %s %02X\n",
				  if_type_msg,
				  page, offset, write ? "W" : "R", values[0]);
		} else {
			idx = 0;
			buf_offset =
			    scnprintf(buf, buf_size, "%s %02X.%02X %s(%d)",
				      if_type_msg, page, offset,
				      write ? "W" : "R", count);

			for (idx = 0; idx < count; idx++) {
				if (0 == (idx & 0x0F))
					buf_offset +=
					    scnprintf(&buf[buf_offset],
						      buf_size - buf_offset,
						      "\n%04X: ", idx);

				buf_offset += scnprintf(&buf[buf_offset],
							buf_size - buf_offset,
							"%02X ", values[idx]);
			}
			buf_offset +=
			    scnprintf(&buf[buf_offset], buf_size - buf_offset,
				      "\n");
		}

		print_formatted_debug_msg(NULL, NULL, -1, buf);
		kfree(buf);
	}
}
#endif /* #if defined(DEBUG) */

static struct mhl_drv_info drv_info = {
	.drv_context_size = sizeof(struct drv_hw_context),
	.mhl_device_initialize = si_mhl_tx_chip_initialize,
	.mhl_device_isr = si_mhl_tx_drv_device_isr,
	.mhl_device_dbg_i2c_reg_xfer = si_device_dbg_i2c_reg_xfer,
	.mhl_device_get_aksv = si_mhl_tx_drv_get_aksv
};

int mhl_tx_write_reg_block_i2c(void *drv_context, u8 page, u8 offset,
			       u16 count, u8 *values)
{
	DUMP_I2C_TRANSFER(page, offset, count, values, true);

	return platform_write_i2c_block(i2c_bus_adapter, page, offset, count,
					values);
}

int mhl_tx_write_reg_i2c(void *drv_context, u8 page, u8 offset, u8 value)
{
	return mhl_tx_write_reg_block_i2c(drv_context, page, offset, 1, &value);
}

int mhl_tx_read_reg_block_i2c(void *drv_context, u8 page, u8 offset,
	u16 count, u8 *values)
{
	int ret;

	if (count == 0) {
		MHL_TX_DBG_ERR("Tried to read 0 bytes\n");
		return -EINVAL;
	}

	ret = platform_read_i2c_block(i2c_bus_adapter, page, offset, count,
		values);
	if (ret != 2) {
		MHL_TX_DBG_ERR("I2c read failed, 0x%02x:0x%02x\n", page,
			offset);
		ret = -EIO;
	} else {
		ret = 0;
		DUMP_I2C_TRANSFER(page, offset, count, values, false);
	}

	return ret;
}

int mhl_tx_read_reg_i2c(void *drv_context, u8 page, u8 offset)
{
	u8 byte_read;
	int status;

	status = mhl_tx_read_reg_block_i2c(drv_context, page, offset,
					   1, &byte_read);

	return status ? status : byte_read;
}

static int i2c_addr_to_spi_cmd(void *drv_context, bool write, u8 *page,
			       u8 *opcode, u8 *dummy_bytes)
{
	if (write) {
		*opcode = spi_op_reg_write;
		*dummy_bytes = 0;
	} else {
		*opcode = spi_op_reg_read;
		*dummy_bytes = 5;
	}

	switch (*page) {
	case SA_TX_PAGE_0:
		*page = 0;
		break;
	case SA_TX_PAGE_1:
		*page = 1;
		break;
	case SA_TX_PAGE_2:
		*page = 2;
		break;
	case SA_TX_PAGE_3:
		*page = 3;
		break;
	case SA_TX_PAGE_4:
		*page = 4;
		break;
	case SA_TX_CBUS:
		*page = 5;
		break;
	case SA_TX_PAGE_6:
		*page = 6;
		break;
	case SA_TX_PAGE_7:
		*page = 7;
		break;
	case SA_TX_PAGE_8:
		*page = 8;
		break;
	default:
		MHL_TX_DBG_ERR("Called with unknown page 0x%02x\n", *page);
		return -EINVAL;
	}
	return 0;
}

inline uint8_t reg_page(uint16_t address)
{
	return (uint8_t)((address >> 8) & 0x00FF);
}

inline uint8_t reg_offset(uint16_t address)
{
	return (uint8_t)(address & 0x00FF);
}

static int mhl_tx_write_reg_block_spi(void *drv_context, u8 page, u8 offset,
	u16 count, u8 *values)
{
	u8 opcode;
	u8 dummy_bytes;
	u16 length = count + 3;
	int ret;

	DUMP_SPI_TRANSFER(page, offset, count, values, true);

	ret = i2c_addr_to_spi_cmd(drv_context, true, &page, &opcode,
				  &dummy_bytes);
	if (ret != 0)
		return ret;

	length = 3 + count + dummy_bytes;

	if (length > MAX_SPI_XFER_BUFFER_SIZE) {
		MHL_TX_DBG_ERR("Transfer count (%d) is too large!\n", count);
		return -EINVAL;
	}

	spi_mem.tx_buf[0] = opcode;
	spi_mem.tx_buf[1] = page;
	spi_mem.tx_buf[2] = offset;
	if (dummy_bytes)
		memset(&spi_mem.tx_buf[3], 0, dummy_bytes);

	memmove(&spi_mem.tx_buf[dummy_bytes + 3], values, count);

	ret = spi_write(spi_dev, spi_mem.tx_buf, length);

	if (ret != 0) {
		MHL_TX_DBG_ERR("SPI write block failed, "
			       "page: 0x%02x, register: 0x%02x\n",
			       page, offset);
		ret = -EIO;
	} else {
		ret = 0;
	}

	return ret;
}

static int mhl_tx_write_reg_spi(void *drv_context, u8 page, u8 offset, u8 value)
{
	return mhl_tx_write_reg_block_spi(drv_context, page, offset, 1, &value);
}

static int mhl_tx_read_reg_block_spi(void *drv_context, u8 page, u8 offset,
	u16 count, u8 *values)
{
	u8 page_num = page;
	u8 opcode;
	u8 dummy_bytes;
	u16 length;
	int ret = 0;

	if (count > MAX_SPI_PAYLOAD_SIZE) {
		MHL_TX_DBG_ERR("Requested transfer count (%d) is too large\n",
			count);
		return -EINVAL;
	}

	ret = i2c_addr_to_spi_cmd(drv_context, false, &page_num, &opcode,
		&dummy_bytes);
	if (ret != 0)
		return ret;

	if ((reg_page(REG_DDC_DATA) == page) &&
		(reg_offset(REG_DDC_DATA) == offset)) {
		dummy_bytes = 11;
		opcode = spi_op_ddc_reg_read;
	}

	length = 3 + count + dummy_bytes;
	if (length > MAX_SPI_XFER_BUFFER_SIZE) {
		MHL_TX_DBG_ERR("Requested transfer total (%d) is too large\n",
			length);
		return -EINVAL;
	}

	spi_message_init(&spi_mem.spi_cmd);
	memset(&spi_mem.spi_xfer, 0, sizeof(spi_mem.spi_xfer));
	spi_mem.tx_buf[0] = opcode;
	spi_mem.tx_buf[1] = page_num;
	spi_mem.tx_buf[2] = offset;

	spi_mem.spi_xfer[0].tx_buf = spi_mem.tx_buf;
	spi_mem.spi_xfer[0].len = 3 + dummy_bytes;
#ifdef USE_SPIOPTIMIZE
	memset(&spi_mem.tx_buf[3], 0, dummy_bytes + count);
	spi_mem.spi_xfer[0].len += count;

	spi_mem.spi_xfer[0].rx_buf = spi_mem.rx_buf;
	spi_message_add_tail(&spi_mem.spi_xfer[0], &spi_mem.spi_cmd);
	ret = spi_sync(spi_dev, &spi_mem.spi_cmd);
#else
	spi_message_add_tail(&spi_mem.spi_xfer[0], &spi_mem.spi_cmd);

	spi_mem.spi_xfer[1].rx_buf = spi_mem.rx_buf;
	spi_mem.spi_xfer[1].len = count;
	spi_mem.spi_xfer[1].cs_change = 1;
	spi_message_add_tail(&spi_mem.spi_xfer[1], &spi_mem.spi_cmd);

	ret = spi_sync(spi_dev, &spi_mem.spi_cmd);
#endif

	if (ret != 0) {
		MHL_TX_DBG_ERR("SPI read block failed, "
			"page: 0x%02x, register: 0x%02x\n",
			page, offset);
	} else {
#ifdef USE_SPIOPTIMIZE
		memcpy(values, &spi_mem.rx_buf[3 + dummy_bytes], count);
#else
		memcpy(values, spi_mem.rx_buf, count);
#endif
		DUMP_SPI_TRANSFER(page, offset, count, values, false);
	}

	return ret;
}

static int mhl_tx_read_reg_block_spi_emsc(void *drv_context, u16 count,
	u8 *values)
{
	u8 dummy_bytes = 1;
	u16 length;
	int ret;

	if (count > MAX_SPI_PAYLOAD_SIZE) {
		MHL_TX_DBG_ERR("Requested transfer count (%d) is too large\n",
			count);
		return -EINVAL;
	}

	length = EMSC_READ_SPI_CMD_SIZE + dummy_bytes + count;
	if (length > MAX_SPI_XFER_BUFFER_SIZE) {
		MHL_TX_DBG_ERR("Requested transfer total (%d) is too large\n",
			length);
		return -EINVAL;
	}

	spi_message_init(&spi_mem.spi_cmd);
	memset(&spi_mem.spi_xfer, 0, sizeof(spi_mem.spi_xfer));
	spi_mem.tx_buf[0] = spi_op_emsc_read;

	spi_mem.spi_xfer[0].tx_buf = spi_mem.tx_buf;
	spi_mem.spi_xfer[0].len = EMSC_READ_SPI_CMD_SIZE + dummy_bytes;

#ifdef USE_SPIOPTIMIZE
	memset(&spi_mem.tx_buf[EMSC_READ_SPI_CMD_SIZE], 0, dummy_bytes + count);
	spi_mem.spi_xfer[0].rx_buf = spi_mem.rx_buf;
	spi_mem.spi_xfer[0].len += count;
	spi_message_add_tail(&spi_mem.spi_xfer[0], &spi_mem.spi_cmd);
#else
	spi_message_add_tail(&spi_mem.spi_xfer[0], &spi_mem.spi_cmd);

	spi_mem.spi_xfer[1].rx_buf = spi_mem.rx_buf;
	spi_mem.spi_xfer[1].len = count;
	spi_mem.spi_xfer[1].cs_change = 1;
	spi_message_add_tail(&spi_mem.spi_xfer[1], &spi_mem.spi_cmd);
#endif
	ret = spi_sync(spi_dev, &spi_mem.spi_cmd);

	if (ret != 0) {
		MHL_TX_DBG_ERR("SPI eMSC read block failed ");
	} else {
#ifdef USE_SPIOPTIMIZE
		memcpy(values,
			&spi_mem.rx_buf[EMSC_READ_SPI_CMD_SIZE + dummy_bytes],
			count);
#else
		memcpy(values, spi_mem.rx_buf, count);
#endif
		/* DUMP_SPI_TRANSFER(page, offset, count, values, false); */
	}

	return ret;
}

int mhl_tx_read_spi_emsc(void *drv_context, u16 count, u8 *values)
{
	u8 *dptr;
	u16 length, block;
	int ret = 0;

	/*
	 * Don't read more than 128 bytes at a time to reduce
	 * possible problems with SPI hardware.
	 */
	dptr = values;
	length = count;
	while (length > 0) {
		block = (length < 128) ? length : 128;
		ret = mhl_tx_read_reg_block_spi_emsc(drv_context, block, dptr);
		if (ret != 0)
			break;
		length -= block;
		dptr += block;
	}

	return ret;
}

static int mhl_tx_read_reg_spi(void *drv_context, u8 page, u8 offset)
{
	u8 byte_read = 0;
	int status;

	status = mhl_tx_read_reg_block_spi(drv_context, page, offset, 1,
		&byte_read);

	return status ? status : byte_read;
}

void mhl_tx_clear_emsc_read_err(void *drv_context)
{

	if (use_spi) {
		spi_mem.tx_buf[0] = spi_op_clear_status;
		spi_mem.tx_buf[1] = 0x02;
		spi_write(spi_dev, spi_mem.tx_buf, 2);
	}
}

int mhl_tx_write_reg_block(void *drv_context, u16 address, u16 count,
	u8 *values)
{
	u8 page = (u8)(address >> 8);
	u8 offset = (u8)address;

	if (use_spi)
		return mhl_tx_write_reg_block_spi(drv_context, page, offset,
			count, values);
	else
		return mhl_tx_write_reg_block_i2c(drv_context, page, offset,
			count, values);
}

void si_mhl_tx_platform_get_block_buffer_info(struct block_buffer_info_t
	*block_buffer_info)
{
	if (use_spi) {
		block_buffer_info->buffer = spi_mem.block_tx_buffers;
		block_buffer_info->req_size = MAX_SPI_EMSC_BLOCK_SIZE;
		block_buffer_info->payload_offset = EMSC_WRITE_SPI_CMD_SIZE;
	} else {
		block_buffer_info->buffer = i2c_mem.block_tx_buffers;
		block_buffer_info->req_size = MAX_I2C_EMSC_BLOCK_SIZE;
		block_buffer_info->payload_offset = MAX_I2C_CMD_SIZE;
	}
}

int mhl_tx_write_block_spi_emsc(void *drv_context, struct block_req *req)
{
	u16 length;
	int ret;

	/* DUMP_SPI_TRANSFER(page, offset, req->count, req->payload->as_bytes,
	 * true);
	 */

	/* dummy bytes will always be zero */
	length = EMSC_WRITE_SPI_CMD_SIZE + req->count;

	if (length > MAX_SPI_EMSC_BLOCK_SIZE) {
		MHL_TX_DBG_ERR("Transfer count (%d) is too large!\n",
			req->count);
		return -EINVAL;
	}

	req->platform_header[0] = spi_op_emsc_write;

	ret = spi_write(spi_dev, req->platform_header, length);

	if (ret != 0) {
		MHL_TX_DBG_ERR("SPI write block failed\n");
		ret = -EIO;
	} else {
		ret = 0;
	}

	return ret;
}

int mhl_tx_write_reg(void *drv_context, u16 address, u8 value)
{
	u8 page = (u8)(address >> 8);
	u8 offset = (u8)address;

	if (use_spi)
		return mhl_tx_write_reg_spi(drv_context, page, offset, value);
	else
		return mhl_tx_write_reg_i2c(drv_context, page, offset, value);
}

int mhl_tx_read_reg_block(void *drv_context, u16 address, u16 count, u8 *values)
{
	u8 page = (u8)(address >> 8);
	u8 offset = (u8)address;

	if (use_spi)
		return mhl_tx_read_reg_block_spi(drv_context, page, offset,
						 count, values);
	else
		return mhl_tx_read_reg_block_i2c(drv_context, page, offset,
						 count, values);
}

int mhl_tx_read_reg(void *drv_context, u16 address)
{
	u8 page = (u8)(address >> 8);
	u8 offset = (u8)address;

	if (use_spi)
		return mhl_tx_read_reg_spi(drv_context, page, offset);
	else
		return mhl_tx_read_reg_i2c(drv_context, page, offset);
}

int mhl_tx_modify_reg(void *drv_context, u16 address, u8 mask, u8 value)
{
	int reg_value;
	int write_status;

	reg_value = mhl_tx_read_reg(drv_context, address);
	if (reg_value < 0)
		return reg_value;

	reg_value &= ~mask;
	reg_value |= mask & value;

	write_status = mhl_tx_write_reg(drv_context, address, reg_value);

	if (write_status < 0)
		return write_status;
	else
		return reg_value;
}

/*
 * Return a value indicating how upstream HPD is
 * implemented on this platform.
 */
enum hpd_control_mode platform_get_hpd_control_mode(void)
{
	return HPD_CTRL_PUSH_PULL;
}

static int gpio_expander_transfer(u8 offset, u16 count, u8 *values, bool write)
{
	struct i2c_msg msg[2];
	u8 buf[8];
	int msg_count;
	int ret;

	if ((count + 1) > ARRAY_SIZE(buf))
		return -1;

	if (write) {
		buf[0] = offset;
		memmove(&buf[1], values, count);

		msg[0].flags = 0;
		msg[0].addr = GPIO_EXP_ADDR >> 1;
		msg[0].buf = buf;
		msg[0].len = count + 1;

		msg_count = 1;

	} else {

		msg[0].flags = 0;
		msg[0].addr = GPIO_EXP_ADDR >> 1;
		msg[0].buf = &offset;
		msg[0].len = 1;

		msg[1].flags = I2C_M_RD;
		msg[1].addr = GPIO_EXP_ADDR >> 1;
		msg[1].buf = values;
		msg[1].len = count;
		msg_count = 2;
	}

	ret = i2c_transfer(i2c_bus_adapter, msg, msg_count);
	if (ret != msg_count) {
		MHL_TX_DBG_ERR("I2c %s failed ret:%d, page: 0x%02x, "
			       "register: 0x%02x count:0x%x\n",
			       write ? "write" : "read", ret, GPIO_EXP_ADDR,
			       offset, count);
		ret = -EIO;
	} else {
		ret = 0;
		/*DUMP_I2C_TRANSFER(GPIO_EXP_ADDR, offset, count, values,
		 * write);
		 */
	}

	return ret;
}

static int gpio_expander_init(void)
{
	u8 gpio_exp_mask_init[] = { GPIO_EXP_INTR_MASK_REGS_OFFSET,
		0xFF, 0xFF, 0xFF, 0xFF, 0xFF
	};
	u8 gpio_exp_pol_invert_init[] = { GPIO_EXP_POL_INVERT_REGS_OFFSET,
		0x00, 0x00, 0x00, 0x00, 0x00
	};
	u8 gpio_exp_output_init[] = { GPIO_EXP_OUTPUT_REGS_OFFSET, 0x00, 0x00,
		GPIO_EXP_BANK_2_OUTPUT_DEFAULT,
		GPIO_EXP_BANK_3_OUTPUT_DEFAULT,
		GPIO_EXP_BANK_4_OUTPUT_DEFAULT
	};
	u8 gpio_exp_io_config_init[] = { GPIO_EXP_IO_CONFIG_REGS_OFFSET,
		0xFF, 0xFF, 0x00, 0x18, 0x0F
	};
	int ret;

	/* First reset GPIO Expander (and 8620) */
	toggle_BB_RST(10);

	ret = gpio_expander_transfer(gpio_exp_mask_init[0],
		ARRAY_SIZE(gpio_exp_mask_init) - 1,
		&gpio_exp_mask_init[1], true);
	if (ret != 0) {
		pr_err("%s():%d gpio_expander_transfer failed, error code %d\n",
		       __func__, __LINE__, ret);
		return ret;
	}

	ret = gpio_expander_transfer(gpio_exp_pol_invert_init[0],
		ARRAY_SIZE(gpio_exp_pol_invert_init) - 1,
		&gpio_exp_pol_invert_init[1], true);
	if (ret != 0) {
		pr_err("%s():%d gpio_expander_transfer failed, error code %d\n",
			__func__, __LINE__, ret);
		return ret;
	}
	ret = gpio_expander_transfer(gpio_exp_output_init[0],
				     ARRAY_SIZE(gpio_exp_output_init) - 1,
				     &gpio_exp_output_init[1], true);
	gpio_exp_bank2_output = GPIO_EXP_BANK_2_OUTPUT_DEFAULT;
	gpio_exp_bank3_output = GPIO_EXP_BANK_3_OUTPUT_DEFAULT;
	gpio_exp_bank4_output = GPIO_EXP_BANK_4_OUTPUT_DEFAULT;

	if (ret != 0) {
		pr_err("%s():%d gpio_expander_transfer failed, error code %d\n",
		       __func__, __LINE__, ret);
		return ret;
	}
	ret = gpio_expander_transfer(gpio_exp_io_config_init[0],
				     ARRAY_SIZE(gpio_exp_io_config_init) - 1,
				     &gpio_exp_io_config_init[1], true);
	if (ret != 0) {
		pr_err("%s():%d gpio_expander_transfer failed, error code %d\n",
		       __func__, __LINE__, ret);
	}
	return ret;
}

#if (LINUX_KERNEL_VER >= 308)
static int starter_kit_init(void)
#else
static int __devinit starter_kit_init(void)
#endif
{
	int ret;

	/* Acquire the GPIO pins needed to control the starter kit. */
	ret = gpio_request_array(starter_kit_control_gpios,
		ARRAY_SIZE(starter_kit_control_gpios));
	if (ret < 0) {
		pr_err("%s(): gpio_request_array failed, error code %d\n",
			__func__, ret);
	} else {
		if (expander_enabled)
			ret = gpio_expander_init();
		else
			toggle_BB_RST(100);
		if (ret != 0) {
			gpio_free_array(starter_kit_control_gpios,
				ARRAY_SIZE(starter_kit_control_gpios));
		}
	}
	return ret;
}

#if defined(SIMG_USE_DTS)
static int si_8620_parse_dt(struct device *dev)
{
	struct device_node *np = dev->of_node;
	int value;
	int pmi_gpio9;
	int pmi_gpio10;
	int pm_gpio2;
	int fw_wake;
	int rc = 0;
	struct pinctrl *pinctrl;
	struct pinctrl_state *active_state;
	struct regulator *ldo;

	pinctrl = devm_pinctrl_get(dev);
	if (IS_ERR_OR_NULL(pinctrl)) {
		MHL_TX_DBG_ERR("%s: failed to get pinctrl\n", __func__);
		rc = -ENODEV;
		goto dt_exit;
	}

	active_state = pinctrl_lookup_state(pinctrl, "mhl_active");
	if (IS_ERR_OR_NULL(active_state)) {
		MHL_TX_DBG_ERR("%s: failed to find mhl_active state\n",
								__func__);
		rc = -ENODEV;
		goto dt_exit;
	}

	if (pinctrl_select_state(pinctrl, active_state)) {
		MHL_TX_DBG_ERR("%s: failed to select mhl active state\n",
								__func__);
	}

	ldo = devm_regulator_get(dev, "ldo25");
	if (IS_ERR_OR_NULL(ldo)) {
		MHL_TX_DBG_ERR("%s: Unable to get regulator\n", __func__);
		rc = -ENODEV;
		goto dt_exit;
	}

	rc = regulator_set_voltage(ldo, 1000000, 1000000);
	if (rc) {
		MHL_TX_DBG_ERR("%s: Unable to set regulator voltage\n",
								__func__);
		goto dt_exit;
	}

	rc = regulator_enable(ldo);
	if (rc) {
		MHL_TX_DBG_ERR("%s: Regulator enable failed\n", __func__);
		goto dt_exit;
	}

	pm_gpio2 = of_get_named_gpio(np, "sil,pwr-gpio", 0);
	pmi_gpio9 = of_get_named_gpio(np, "sil,pmi9", 0);
	pmi_gpio10 = of_get_named_gpio(np, "sil,pmi10", 0);
	fw_wake = of_get_named_gpio(np, "sil,fw-wake", 0);

	if (gpio_is_valid(pmi_gpio9)) {
		rc = gpio_request(pmi_gpio9, "mhl_pmi9");
		if (rc) {
			MHL_TX_DBG_ERR("%s:power_gpio=[%d] req failed:\n",
				__func__, pmi_gpio9);
			goto dt_exit;
		}
		gpio_direction_output(pmi_gpio9, 0);
		gpio_set_value(pmi_gpio9, 1);
	} else {
		MHL_TX_DBG_ERR("%s:power_gpio=[%d] invalid\n",
				__func__, pmi_gpio9);
		rc = pmi_gpio9;
		goto dt_exit;
	}

	if (gpio_is_valid(pmi_gpio10)) {
		rc = gpio_request(pmi_gpio10, "mhl_pmi10");
		if (rc) {
			MHL_TX_DBG_ERR("%s:power_gpio=[%d] req failed:\n",
				__func__, pmi_gpio10);
			goto dt_exit;
		}
		gpio_direction_output(pmi_gpio10, 0);
		gpio_set_value(pmi_gpio10, 1);
	} else {
		MHL_TX_DBG_ERR("%s:power_gpio=[%d] invalid\n",
				__func__, pmi_gpio10);
		rc = pmi_gpio10;
		goto dt_exit;
	}

	if (gpio_is_valid(pm_gpio2)) {
		rc = gpio_request(pm_gpio2, "mhl_pm2");
		if (rc) {
			MHL_TX_DBG_ERR("%s:power_gpio=[%d] req failed:\n",
				__func__, pm_gpio2);
			goto dt_exit;
		}
		gpio_direction_output(pm_gpio2, 0);
		gpio_set_value(pm_gpio2, 0);
	} else {
		MHL_TX_DBG_ERR("%s:power_gpio=[%d] invalid\n",
				__func__, pm_gpio2);
		rc = pm_gpio2;
		goto dt_exit;
	}

	if (gpio_is_valid(fw_wake)) {
		rc = gpio_request(fw_wake, "mhl_fw");
		if (rc) {
			MHL_TX_DBG_ERR("%s:wake_gpio=[%d] req failed:\n",
				__func__, fw_wake);
			return rc;
		}
		gpio_direction_output(fw_wake, 0);
		gpio_set_value(fw_wake, 1);
		platform_signals[TX_FW_WAKE].gpio_number = fw_wake;
	} else {
		MHL_TX_DBG_ERR("%s:wake_gpio=[%d] invalid\n",
				__func__, fw_wake);
		rc = fw_wake;
		goto dt_exit;
	}

	value = of_get_named_gpio_flags(np, "sil,reset-gpio", 0, NULL);
	if (value >= 0) {
		starter_kit_control_gpios[MHL_RESET_INDEX].gpio = value;
		gpio_set_value(starter_kit_control_gpios[MHL_RESET_INDEX].gpio,
									1);
	}

	value = of_get_named_gpio_flags(np, "sil,irq-gpio", 0, NULL);
	if (value >= 0)
		starter_kit_control_gpios[MHL_INT_INDEX].gpio = value;

	/*
	 * Need this for I/O expander in case we're using SPI as
	 * the register I/O.
	 */
	if (!of_property_read_u32(np, "sil,i2c_port#", &value))
		i2c_adapter_num = value;

	MHL_TX_DBG_INFO("Resources assigned to driver...\n");
	MHL_TX_DBG_INFO("    Reset GPIO = %d\n",
			starter_kit_control_gpios[MHL_RESET_INDEX].gpio);
	MHL_TX_DBG_INFO("    Interrupt GPIO = %d\n",
			starter_kit_control_gpios[MHL_INT_INDEX].gpio);
	MHL_TX_DBG_INFO("    I2C adapter = %d\n", i2c_adapter_num);
	if (use_spi)
		MHL_TX_DBG_ERR("    SPI adapter = %d\n", spi_bus_num);

dt_exit:
	return rc;
}
#endif

#if (LINUX_KERNEL_VER >= 308)
static int si_8620_mhl_tx_i2c_probe(struct i2c_client *client,
#else
static int __devinit si_8620_mhl_tx_i2c_probe(struct i2c_client *client,
#endif
	const struct i2c_device_id *id)
{
	int ret;

	pr_info("%s(), i2c_device_id = %pK\n", __func__, id);

#if defined(SIMG_USE_DTS)
	/*
	 * Modify default driver platform parameters with those
	 * specified in the device tree.
	 */
	if (client->dev.of_node) {
		ret = si_8620_parse_dt(&client->dev);
		if (ret)
			goto done;
	}
#endif
	i2c_bus_adapter = to_i2c_adapter(client->dev.parent);
	device_addresses[0].client = client;

	if (!i2c_bus_adapter ||
		!i2c_check_functionality(i2c_bus_adapter,
			I2C_FUNC_SMBUS_BYTE_DATA)) {
		MHL_TX_DBG_ERR("[ERROR] i2c function check failed\n");
		ret = -EIO;
		goto done;
	}
	i2c_mem.block_tx_buffers = kmalloc(MAX_I2C_EMSC_BLOCK_SIZE *
		NUM_BLOCK_QUEUE_REQUESTS, GFP_KERNEL);
	if (NULL == i2c_mem.block_tx_buffers) {
		ret = -ENOMEM;
		goto done;
	}

	ret = starter_kit_init();
	if (ret >= 0) {
		drv_info.irq = gpio_to_irq(
			starter_kit_control_gpios[MHL_INT_INDEX].gpio);
		ret = mhl_tx_init(&drv_info, &client->dev);
		if (ret) {
			MHL_TX_DBG_ERR("mhl_tx_init failed, error code %d\n",
				ret);
			gpio_free_array(starter_kit_control_gpios,
				ARRAY_SIZE(starter_kit_control_gpios));
		}
	}
done:
	if (ret != 0) {
		kfree(i2c_mem.block_tx_buffers);
		probe_fail = true;
	}
	return ret;
}

#if (LINUX_KERNEL_VER >= 308)
static int si_8620_mhl_tx_remove(struct i2c_client *client)
#else
static int __devexit si_8620_mhl_tx_remove(struct i2c_client *client)
#endif
{
	if (!use_spi)
		kfree(i2c_mem.block_tx_buffers);

	gpio_free_array(starter_kit_control_gpios,
		ARRAY_SIZE(starter_kit_control_gpios));
	return 0;
}

int si_8620_pm_suspend(struct device *dev)
{
	int status = -EINVAL;

	if (dev == 0)
		goto done;

	status = down_interruptible(&platform_lock);
	if (status)
		goto done;

	status = mhl_handle_power_change_request(dev, false);
	/*
	 * Set MHL/USB switch to USB
	 * NOTE: Switch control is implemented differently on each
	 * version of the starter kit.
	 */
	set_pin(X02_USB_SW_CTRL, 0);

	up(&platform_lock);
done:
	return status;
}

int si_8620_pm_resume(struct device *dev)
{
	int status = -EINVAL;

	if (dev == 0)
		goto done;

	status = down_interruptible(&platform_lock);
	if (status)
		goto done;

	set_pin(X02_USB_SW_CTRL, 1);
	status = mhl_handle_power_change_request(dev, true);

	up(&platform_lock);
done:
	return status;
}

int si_8620_power_control(bool power_up)
{
	struct device *dev = NULL;
	int status;

	if (use_spi)
		dev = &spi_dev->dev;
	else
		dev = &device_addresses[0].client->dev;

	if (power_up)
		status = si_8620_pm_resume(dev);
	else
		status = si_8620_pm_suspend(dev);
	return status;
}
EXPORT_SYMBOL_GPL(si_8620_power_control);

int si_8620_get_hpd_status(int *hpd_status)
{
	struct device *dev = NULL;
	int status = 0;
	struct mhl_dev_context *dev_context;

	if (use_spi)
		dev = &spi_dev->dev;
	else
		dev = &device_addresses[0].client->dev;

	dev_context = dev_get_drvdata(dev);

	if (down_interruptible(&dev_context->isr_lock)) {
		MHL_TX_DBG_ERR("%scouldn't acquire mutex%s\n",
			ANSI_ESC_RED_TEXT, ANSI_ESC_RESET_TEXT);
		return  -ERESTARTSYS;
	}

	if (dev_context->dev_flags & DEV_FLAG_SHUTDOWN) {
		MHL_TX_DBG_ERR("%sshutting down%s\n",
			ANSI_ESC_YELLOW_TEXT, ANSI_ESC_RESET_TEXT);
		status = -ENODEV;
	} else {
		*hpd_status = si_mhl_tx_drv_get_hpd_status(dev_context);
		MHL_TX_DBG_INFO("%HPD status: %s%d%s\n",
			ANSI_ESC_YELLOW_TEXT,
			*hpd_status,
			ANSI_ESC_RESET_TEXT);
	}
	up(&dev_context->isr_lock);
	return status;
}
EXPORT_SYMBOL_GPL(si_8620_get_hpd_status);

int si_8620_get_hdcp2_status(uint32_t *hdcp2_status)
{
	struct device *dev = NULL;
	int status = 0;
	struct mhl_dev_context *dev_context;

	if (use_spi)
		dev = &spi_dev->dev;
	else
		dev = &device_addresses[0].client->dev;

	dev_context = dev_get_drvdata(dev);

	if (down_interruptible(&dev_context->isr_lock)) {
		MHL_TX_DBG_ERR("%scouldn't acquire mutex%s\n",
			ANSI_ESC_RED_TEXT, ANSI_ESC_RESET_TEXT);
		return  -ERESTARTSYS;
	}

	if (dev_context->dev_flags & DEV_FLAG_SHUTDOWN) {
		MHL_TX_DBG_ERR("%sshutting down%s\n",
			ANSI_ESC_YELLOW_TEXT, ANSI_ESC_RESET_TEXT);
		status = -ENODEV;
	} else {
		*hdcp2_status = si_mhl_tx_drv_get_hdcp2_status(dev_context);
		MHL_TX_DBG_INFO("%HDCP2 status: %s0x%8x%s\n",
			ANSI_ESC_YELLOW_TEXT,
			*hdcp2_status,
			ANSI_ESC_RESET_TEXT);
	}
	up(&dev_context->isr_lock);
	return status;
}
EXPORT_SYMBOL_GPL(si_8620_get_hdcp2_status);

static const struct i2c_device_id si_8620_mhl_tx_id[] = {
	{MHL_DEVICE_NAME, 0},
	{}
};

MODULE_DEVICE_TABLE(i2c, si_8620_mhl_tx_id);

static const struct dev_pm_ops si_8620_tx_pm_ops = {
	.runtime_suspend = si_8620_pm_suspend,
	.runtime_resume = si_8620_pm_resume,
};

#ifdef SIMG_USE_DTS
static struct of_device_id si_8620_of_match_table[] = {
	{
		.compatible = "sil,sii-8620",
	},
	{}
};
#endif

static struct i2c_driver si_8620_mhl_tx_i2c_driver = {
	.driver = {
		   .owner = THIS_MODULE,
		   .name = MHL_DRIVER_NAME,
#ifdef SIMG_USE_DTS
		   .of_match_table = si_8620_of_match_table,
#endif
		   .pm = &si_8620_tx_pm_ops,
		   },
	.id_table = si_8620_mhl_tx_id,
	.probe = si_8620_mhl_tx_i2c_probe,

#if (LINUX_KERNEL_VER >= 308)
	.remove = si_8620_mhl_tx_remove,
#else
	.remove = __devexit_p(si_8620_mhl_tx_remove),
#endif
	.command = NULL,
};

#ifndef SIMG_USE_DTS
static struct i2c_board_info __initdata si_8620_i2c_boardinfo[] = {
	{
	 I2C_BOARD_INFO(MHL_DEVICE_NAME, (SA_TX_PAGE_0 >> 1)),
	 .flags = I2C_CLIENT_WAKE,
	 .irq = -1
	 }
};
#endif

#if (LINUX_KERNEL_VER >= 308)
static int si_8620_mhl_tx_spi_probe(struct spi_device *spi)
#else
static int __devinit si_8620_mhl_tx_spi_probe(struct spi_device *spi)
#endif
{
	int ret;

	pr_info("%s(), spi = %pK\n", __func__, spi);
	spi->bits_per_word = 8;
	spi_dev = spi;
	spi_bus_num = spi->master->bus_num;

#if defined(SIMG_USE_DTS)
	/*
	 * Modify default driver platform parameters with those
	 * specified in the device tree.
	 */
	if (spi_dev->dev.of_node) {
		ret = si_8620_parse_dt(&spi_dev->dev);
		if (ret)
			goto failed;
	}

	/*
	 * Because we're using SPI for register access, we need to
	 * obtain separate i2c access for the I/O expander, so we
	 * use the I2C adapter number we got from the device tree.
	 */
	i2c_bus_adapter = i2c_get_adapter(i2c_adapter_num);
	if (i2c_bus_adapter == NULL) {
		pr_err("%s() failed to get i2c adapter %d\n", __func__,
			i2c_adapter_num);
		ret = -EFAULT;
		goto failed;
	}

	if (!i2c_check_functionality(i2c_bus_adapter,
		I2C_FUNC_SMBUS_BYTE_DATA)) {
		MHL_TX_DBG_ERR("[ERROR] i2c function check failed\n");
		ret = -EIO;
		goto failed;
	}
#endif

	spi_mem.tx_buf = kmalloc(MAX_SPI_XFER_BUFFER_SIZE, GFP_KERNEL);
	spi_mem.rx_buf = kmalloc(MAX_SPI_XFER_BUFFER_SIZE, GFP_KERNEL);
	spi_mem.block_tx_buffers = kmalloc(MAX_SPI_EMSC_BLOCK_SIZE *
		NUM_BLOCK_QUEUE_REQUESTS, GFP_KERNEL);
	if (!spi_mem.tx_buf || !spi_mem.rx_buf || !spi_mem.block_tx_buffers) {
		ret = -ENOMEM;
		goto mem_cleanup;
	}
	ret = starter_kit_init();
	if (ret >= 0) {
		drv_info.irq = gpio_to_irq(
			starter_kit_control_gpios[MHL_INT_INDEX].gpio);
		ret = mhl_tx_init(&drv_info, &spi_dev->dev);
		if (ret) {
			pr_err("%s(): mhl_tx_init failed, error code %d\n",
				__func__, ret);
			gpio_free_array(starter_kit_control_gpios,
					ARRAY_SIZE(starter_kit_control_gpios));
			goto mem_cleanup;
		}
		goto done;
	}

mem_cleanup:
	kfree(spi_mem.tx_buf);
	spi_mem.tx_buf = NULL;

	kfree(spi_mem.rx_buf);
	spi_mem.rx_buf = NULL;

	kfree(spi_mem.block_tx_buffers);
	spi_mem.block_tx_buffers = NULL;

#if defined(SIMG_USE_DTS)
failed:
#endif
	probe_fail = true;

done:
	return ret;
}

#if (LINUX_KERNEL_VER >= 308)
static int si_8620_mhl_spi_remove(struct spi_device *spi_dev)
#else
static int __devexit si_8620_mhl_spi_remove(struct spi_device *spi_dev)
#endif
{
	pr_info("%s() called\n", __func__);

	gpio_free_array(starter_kit_control_gpios,
			ARRAY_SIZE(starter_kit_control_gpios));
	kfree(spi_mem.tx_buf);
	kfree(spi_mem.rx_buf);
	kfree(spi_mem.block_tx_buffers);
	return 0;
}

static struct spi_driver si_86x0_mhl_tx_spi_driver = {
	.driver = {
		.owner = THIS_MODULE,
		.name = MHL_DRIVER_NAME,
#ifdef SIMG_USE_DTS
		.of_match_table = si_8620_of_match_table,
#endif
		.pm = &si_8620_tx_pm_ops,
		},
	.probe = si_8620_mhl_tx_spi_probe,
#if (LINUX_KERNEL_VER >= 308)
	.remove = si_8620_mhl_spi_remove,
#else
	.remove = __devexit_p(si_8620_mhl_spi_remove),
#endif
};

#ifndef SIMG_USE_DTS
static struct spi_board_info __initdata si_86x0_spi_board_info = {
	.modalias = MHL_DRIVER_NAME,
	.max_speed_hz = SPI_BUS_SPEED,
	.bus_num = SPI_BUS_NUM,
	.chip_select = SPI_CHIP_SEL,
	.mode = SPI_TRANSFER_MODE,
	.irq = -1
};

static int __init add_spi_device_to_bus(void)
{
	struct spi_master *spi_master;
	int status = 0;

	pr_info("add_spi_device_to_bus called\n");

	spi_master = spi_busnum_to_master(SPI_BUS_NUM);
	if (spi_master == NULL) {
		pr_err("spi_busnum_to_master(%d) returned NULL\n", SPI_BUS_NUM);
		return -1;
	}

	spi_dev = spi_new_device(spi_master, &si_86x0_spi_board_info);
	if (spi_dev == NULL || probe_fail) {
		pr_err("spi_new_device() failed\n");
		if (spi_dev)
			spi_unregister_device(spi_dev);
		status = -1;
		goto exit;
	}

exit:
	put_device(&spi_master->dev);
	return status;
}
#endif

static int __init spi_init(void)
{
	int status;

	status = spi_register_driver(&si_86x0_mhl_tx_spi_driver);
	if (status < 0) {
		pr_err("[ERROR] %s():%d failed !\n", __func__, __LINE__);
		goto done;
	}

#ifndef SIMG_USE_DTS
	status = add_spi_device_to_bus();
	if (status < 0)
		MHL_TX_DBG_ERR("Failed to add MHL transmitter as SPI device\n");
#endif

	if (probe_fail || status < 0) {
		spi_unregister_driver(&si_86x0_mhl_tx_spi_driver);
		status = -ENODEV;
	}
done:
	return status;
}

static int __init i2c_init(void)
{
#ifndef SIMG_USE_DTS
	struct i2c_client *client;
	int idx;
#endif
	int ret = -ENODEV;

#ifndef SIMG_USE_DTS
	/* "Hotplug" the MHL transmitter device onto the 2nd I2C bus */
	i2c_bus_adapter = i2c_get_adapter(I2C_ADAPTER);
	if (i2c_bus_adapter == NULL)
		goto done;

	for (idx = 0; idx < ARRAY_SIZE(device_addresses); idx++) {
		if (idx == 0) {
			client =
			    i2c_new_device(i2c_bus_adapter,
					   &si_8620_i2c_boardinfo[idx]);
			device_addresses[idx].client = client;
		} else {
			device_addresses[idx].client =
			    i2c_new_dummy(i2c_bus_adapter,
					  device_addresses[idx].dev_addr);
		}
		if (device_addresses[idx].client == NULL) {
			MHL_TX_DBG_INFO("returning %d\n", ret);
			goto err_exit;
		}
	}
#endif
	ret = i2c_add_driver(&si_8620_mhl_tx_i2c_driver);
	if (ret < 0 || probe_fail) {
		if (ret == 0)
			i2c_del_driver(&si_8620_mhl_tx_i2c_driver);
		MHL_TX_DBG_INFO("failed !\n\nCHECK POWER AND CONNECTION "
						"TO CP8620 Starter Kit.\n\n");
		goto err_exit;
	}

	goto done;

err_exit:
#ifndef SIMG_USE_DTS
	for (idx = 0; idx < ARRAY_SIZE(device_addresses); idx++) {
		if (device_addresses[idx].client != NULL)
			i2c_unregister_device(device_addresses[idx].client);
	}
#endif

done:
	if (probe_fail)
		ret = -ENODEV;

	MHL_TX_DBG_INFO("returning %d\n", ret);
	return ret;
}

static int __init si_8620_init(void)
{
	int ret;

	pr_info("mhl: Starting SiI%d Driver v%s\n", MHL_PRODUCT_NUM,
		buildVersion);
	pr_info("mhl: %s\n", buildTime);
#if (INCLUDE_HID == 1)
	pr_info("mhl: Supports MHL3 HID\n");
#endif

	sema_init(&platform_lock, 1);

	platform_flags &= ~PLATFORM_FLAG_HEARTBEAT_MASK;
	switch (use_heartbeat) {
	case 0:
		/* don't do anything with heatbeat */
		break;
	case 1:
		platform_flags |= PLATFORM_VALUE_ISSUE_HEARTBEAT;
		break;
	case 2:
		platform_flags |= PLATFORM_VALUE_DISCONN_HEARTBEAT;
		break;
	default:
		MHL_TX_DBG_ERR("%sinvalid use_heartbeat parameter%s\n",
			ANSI_ESC_RED_TEXT, ANSI_ESC_RESET_TEXT);
	}

	if (tmds_link_speed == 15)
		platform_flags |= PLATFORM_FLAG_1_5GBPS;
	else if (tmds_link_speed == 3)
		platform_flags |= PLATFORM_FLAG_3GBPS;
	else if (tmds_link_speed == 6)
		platform_flags |= PLATFORM_FLAG_6GBPS;

	/*
	 * NOTE: Even if the user selects to communicate with the MHL
	 * transmitter via SPI we still need I2C to communicate with
	 * other devices on the starter kit board.
	 */
#ifndef SIMG_USE_DTS
	i2c_bus_adapter = i2c_get_adapter(I2C_ADAPTER);
	if (i2c_bus_adapter == NULL) {
		pr_err("%s() failed to get i2c adapter %d\n", __func__,
			(int)I2C_ADAPTER);
		return -EFAULT;
	}
#endif
	if (use_spi)
		ret = spi_init();
	else
		ret = i2c_init();

	return ret;
}

static void __exit si_8620_exit(void)
{
#ifndef SIMG_USE_DTS
	int idx;
#endif

	pr_info("si_8620_exit called\n");

	si_8620_power_control(false);

	if (use_spi) {
		mhl_tx_remove(&spi_dev->dev);
		spi_unregister_driver(&si_86x0_mhl_tx_spi_driver);
#ifndef SIMG_USE_DTS
		spi_unregister_device(spi_dev);
#endif
	} else {
		if (device_addresses[0].client != NULL) {
			mhl_tx_remove(&device_addresses[0].client->dev);
			MHL_TX_DBG_INFO("client removed\n");
		}
		i2c_del_driver(&si_8620_mhl_tx_i2c_driver);
		MHL_TX_DBG_INFO("i2c driver deleted from context\n");

#ifndef SIMG_USE_DTS
		for (idx = 0; idx < ARRAY_SIZE(device_addresses); idx++) {
			MHL_TX_DBG_INFO("\n");
			if (device_addresses[idx].client != NULL) {
				MHL_TX_DBG_INFO("unregistering device:%pK\n",
						device_addresses[idx].client);
				i2c_unregister_device(device_addresses[idx].
						      client);
			}
		}
#endif
	}
	MHL_TX_DBG_ERR("driver unloaded.\n");
}

static int debug_level_stack[15];
static unsigned int debug_level_stack_ptr;

void push_debug_level(int new_verbosity)
{
	if (debug_level_stack_ptr < ARRAY_SIZE(debug_level_stack)) {
		/* stack is initially empty */
		debug_level_stack[debug_level_stack_ptr++] = debug_level;
		debug_level = new_verbosity;
	} else {
		MHL_TX_DBG_ERR("%sdebug_level_stack overflowed%s\n",
			       ANSI_ESC_RED_TEXT, ANSI_ESC_RESET_TEXT);
	}
}

void pop_debug_level(void)
{
	if (debug_level_stack_ptr > 0) {
		if (debug_level_stack_ptr > ARRAY_SIZE(debug_level_stack)) {
			MHL_TX_DBG_ERR("%sdebug_level_stack overflowed%s\n",
				       ANSI_ESC_RED_TEXT, ANSI_ESC_RESET_TEXT);
		} else {
			debug_level =
			    debug_level_stack[--debug_level_stack_ptr];
		}
	}
}

int si_8620_register_callbacks(struct si_mhl_callback_api_t *p_callbacks)
{
	struct device *dev = NULL;
	int status = 0;
	struct mhl_dev_context *dev_context;
	struct drv_hw_context *hw_context;

	if (use_spi)
		dev = &spi_dev->dev;
	else
		dev = &device_addresses[0].client->dev;

	dev_context = dev_get_drvdata(dev);
	hw_context = (struct drv_hw_context *)&dev_context->drv_context;

	if (down_interruptible(&dev_context->isr_lock))
		return -ERESTARTSYS;

	if (dev_context->dev_flags & DEV_FLAG_SHUTDOWN) {
		status = -ENODEV;
	} else {
		if (NULL != p_callbacks) {
			if (p_callbacks->context)
				hw_context->
					callbacks.context =
					p_callbacks->context;
			if (p_callbacks->display_timing_enum_begin)
				hw_context->
					callbacks.display_timing_enum_begin =
					p_callbacks->display_timing_enum_begin;
			if (p_callbacks->display_timing_enum_item)
				hw_context->
					callbacks.display_timing_enum_item =
					p_callbacks->display_timing_enum_item;
			if (p_callbacks->display_timing_enum_end)
				hw_context->
					callbacks.display_timing_enum_end =
					p_callbacks->display_timing_enum_end;
			if (p_callbacks->hpd_driven_low)
				hw_context->
					callbacks.hpd_driven_low =
					p_callbacks->hpd_driven_low;
			if (p_callbacks->hpd_driven_high)
				hw_context->
					callbacks.hpd_driven_high =
					p_callbacks->hpd_driven_high;
		}
	}

	up(&dev_context->isr_lock);
	return status;
}
EXPORT_SYMBOL(si_8620_register_callbacks);

int si_8620_info_frame_change(enum hpd_high_callback_status mode_parm,
	union avif_or_cea_861_dtd_u *p_avif_or_dtd,
	size_t avif_or_dtd_max_length, union vsif_mhl3_or_hdmi_u *p_vsif,
	size_t vsif_max_length)
{
	struct device *dev = NULL;
	int status;
	struct mhl_dev_context *dev_context;
	struct drv_hw_context *hw_context;

	if (use_spi)
		dev = &spi_dev->dev;
	else
		dev = &device_addresses[0].client->dev;

	dev_context = dev_get_drvdata(dev);
	hw_context = (struct drv_hw_context *)&dev_context->drv_context;

	if (down_interruptible(&dev_context->isr_lock))
		return -ERESTARTSYS;

	if (dev_context->dev_flags & DEV_FLAG_SHUTDOWN) {
		status = -ENODEV;
	} else {
		size_t xfer_size;

		memset(&hw_context->vsif_mhl3_or_hdmi_from_callback, 0,
			sizeof(hw_context->vsif_mhl3_or_hdmi_from_callback));
		memset(&hw_context->avif_or_dtd_from_callback, 0,
			sizeof(hw_context->avif_or_dtd_from_callback));

		if (sizeof(hw_context->vsif_mhl3_or_hdmi_from_callback) <
			vsif_max_length) {
			xfer_size = sizeof(
				hw_context->vsif_mhl3_or_hdmi_from_callback);
		} else {
			xfer_size = vsif_max_length;
		}
		memcpy(&hw_context->vsif_mhl3_or_hdmi_from_callback, p_vsif,
			xfer_size);

		if (sizeof(hw_context->avif_or_dtd_from_callback) <
			avif_or_dtd_max_length) {
			xfer_size = sizeof(
				hw_context->avif_or_dtd_from_callback);
		} else {
			xfer_size = avif_or_dtd_max_length;
		}
		memcpy(&hw_context->avif_or_dtd_from_callback, p_avif_or_dtd,
			xfer_size);

		status = si_mhl_tx_drv_set_display_mode(dev_context, mode_parm);
	}

	up(&dev_context->isr_lock);
	return status;
}
EXPORT_SYMBOL(si_8620_info_frame_change);

module_init(si_8620_init);
module_exit(si_8620_exit);

MODULE_DESCRIPTION("Silicon Image MHL Transmitter driver");
MODULE_AUTHOR("Silicon Image <http://www.siliconimage.com>");
MODULE_LICENSE("GPL");
