blob: 74f875134b79c2dd02773f5fa65e97fdbaff5eee [file] [log] [blame]
/*
* Copyright (C) 2011 Samsung Electronics
*
* Authors: Adam Hampson <ahampson@sta.samsung.com>
* Erik Gilling <konkers@android.com>
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/sii9234.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/usb/otg_id.h>
#define T_SRC_VBUS_CBUS_TO_STABLE 200
#define T_SRC_WAKE_PULSE_WIDTH_1 19
#define T_SRC_WAKE_PULSE_WIDTH_2 60
#define T_SRC_WAKE_TO_DISCOVER 500
#define T_SRC_VBUS_CBUS_T0_STABLE 500
#define T_SRC_CBUS_FLOAT 50
#define T_WAIT_TIMEOUT_RSEN_INT 200
#define T_SRC_RXSENSE_DEGLITCH 110
/* MHL feature flags */
#define MHL_FEATURE_FLAG_RCP_SUPPORT (1 << 0)
/* MHL TX Addr 0x72 Registers */
#define MHL_TX_IDL_REG 0x02
#define MHL_TX_IDH_REG 0x03
#define MHL_TX_REV_REG 0x04
#define MHL_TX_SRST 0x05
#define MHL_TX_INTR1_REG 0x71
#define MHL_TX_INTR2_REG 0x72 /* Not Documented */
#define MHL_TX_INTR3_REG 0x73 /* Not Documented */
#define MHL_TX_INTR4_REG 0x74
#define MHL_TX_INTR1_ENABLE_REG 0x75
#define MHL_TX_INTR2_ENABLE_REG 0x76 /* Not Documented */
#define MHL_TX_INTR3_ENABLE_REG 0x77 /* Not Documented */
#define MHL_TX_INTR4_ENABLE_REG 0x78
#define MHL_TX_INT_CTRL_REG 0x79
#define INTR_POLARITY (1 << 1)
#define INTR_OPEN_DRAIN (1 << 2)
#define HPD_OUT_OVR_EN (1 << 4)
#define HPD_OUT_OVR_VAL (1 << 5)
#define HPD_OUT_OPEN_DRAIN (1 << 6)
#define MHL_TX_TMDS_CCTRL 0x80
#define MHL_TX_DISC_CTRL1_REG 0x90
#define MHL_TX_DISC_CTRL2_REG 0x91
#define MHL_TX_DISC_CTRL3_REG 0x92
#define MHL_TX_DISC_CTRL4_REG 0x93 /* Not Documented */
/* There doesn't seem to be any documentation for CTRL5 but it looks like
* it is some sort of pull up control register
*/
#define MHL_TX_DISC_CTRL5_REG 0x94
#define MHL_TX_DISC_CTRL6_REG 0x95
#define MHL_TX_DISC_CTRL7_REG 0x96
#define MHL_TX_DISC_CTRL8_REG 0x97 /* Not Documented */
#define MHL_TX_STAT1_REG 0x98 /* Not Documented */
#define MHL_TX_STAT2_REG 0x99
#define MHL_TX_MHLTX_CTL1_REG 0xA0
#define MHL_TX_MHLTX_CTL2_REG 0xA1
#define MHL_TX_MHLTX_CTL4_REG 0xA3
#define MHL_TX_MHLTX_CTL6_REG 0xA5
#define MHL_TX_MHLTX_CTL7_REG 0xA6
/* MHL TX SYS STAT Registers
* Not Documented, mentioned only in reference of RSEN
*/
#define MHL_TX_SYSSTAT_REG 0x09
/* MHL TX SYS STAT Register Bits */
#define RSEN_STATUS (1<<2)
/* MHL TX INTR4 Register Bits */
#define RGND_READY_INT (1<<6)
#define VBUS_LOW_INT (1<<5)
#define CBUS_LKOUT_INT (1<<4)
#define MHL_DISC_FAIL_INT (1<<3)
#define MHL_EST_INT (1<<2)
/* MHL TX INTR4_ENABLE 0x78 Register Bits */
#define RGND_READY_MASK (1<<6)
#define CBUS_LKOUT_MASK (1<<4)
#define MHL_DISC_FAIL_MASK (1<<3)
#define MHL_EST_MASK (1<<2)
/* MHL TX INTR1 Register Bits*/
#define HPD_CHANGE_INT (1<<6)
#define RSEN_CHANGE_INT (1<<5)
/* MHL TX INTR1_ENABLE 0x75 Register Bits*/
#define HPD_CHANGE_INT_MASK (1<<6)
#define RSEN_CHANGE_INT_MASK (1<<5)
#define CBUS_CONFIG_REG 0x07
#define CBUS_INT_STATUS_1_REG 0x08
#define CBUS_INT_1_MASK 0x09
#define CBUS_MSC_COMMAND_START 0x12
#define START_MSC_RESERVED (1 << 0)
#define START_MSC_MSG (1 << 1)
#define START_READ_DEVCAP (1 << 2)
#define START_WRITE_STAT_INT (1 << 3)
#define START_WRITE_BURST (1 << 4)
#define CBUS_MSC_RAP_POLL 0x00
#define CBUS_MSC_RAP_CONTENT_ON 0x10
#define CBUS_MSC_RAP_CONTENT_OFF 0x11
#define CBUS_MSC_OFFSET_REG 0x13
#define CBUS_MSC_FIRST_DATA_OUT 0x14
#define CBUS_MSC_SECOND_DATA_OUT 0x15
#define CBUS_MSC_FIRST_DATA_IN 0x16
#define CBUS_MSC_MSG_CMD_IN 0x18
#define CBUS_MSC_MSG_DATA_IN 0x19
#define CBUS_INT_STATUS_2_REG 0x1E
#define CBUS_INT_2_MASK 0x1F
#define CBUS_LINK_CONTROL_2_REG 0x31
#define CBUS_INT_STATUS_2_REG 0x1E
/* MHL Interrupt Registers */
#define CBUS_MHL_INTR_REG_0 0xA0
#define MHL_INT_DCAP_CHG (1<<0)
#define MHL_INT_DSCR_CHG (1<<1)
#define MHL_INT_REQ_WRT (1<<2)
#define MHL_INT_GRT_WRT (1<<3)
#define CBUS_MHL_INTR_REG_1 0xA1
#define MHL_INT_EDID_CHG (1<<1)
#define CBUS_MHL_INTR_REG_2 0xA2
#define CBUS_MHL_INTR_REG_3 0xA3
/* MHL Status Registers */
#define CBUS_MHL_STATUS_REG_0 0xB0
#define MHL_STATUS_DCAP_READY (1<<0)
#define CBUS_MHL_STATUS_REG_1 0xB1
#define MHL_STATUS_CLK_NORMAL ((1<<0) | (1<<1))
#define MHL_STATUS_CLK_PACKEDPIXEL (1<<1)
#define MHL_STATUS_PATH_ENABLED (1<<3)
#define MHL_STATUS_MUTED (1<<4)
#define CBUS_MHL_STATUS_REG_2 0xB2
#define CBUS_MHL_STATUS_REG_3 0xB3
/* Device interrupt register offset of connected device */
#define CBUS_MHL_INTR_OFFSET_0 0x20 /* RCHANGE_INT */
#define CBUS_MHL_INTR_OFFSET_1 0x21 /* DCHANGE_INT */
#define CBUS_MHL_INTR_OFFSET_2 0x22
#define CBUS_MHL_INTR_OFFSET_3 0x23
/* Device status register offset of connected device */
#define CBUS_MHL_STATUS_OFFSET_0 0x30 /* CONNECTED_RDY */
#define CBUS_MHL_STATUS_OFFSET_1 0x31 /* LINK_MODE */
#define CBUS_MHL_STATUS_OFFSET_2 0x32
#define CBUS_MHL_STATUS_OFFSET_3 0x33
/* CBUS INTR1 STATUS Register bits */
#define MSC_RESP_ABORT (1<<6)
#define MSC_REQ_ABORT (1<<5)
#define MSC_REQ_DONE (1<<4)
#define MSC_MSG_RECD (1<<3)
#define CBUS_DDC_ABORT (1<<2)
/* CBUS INTR1 STATUS 0x09 Enable Mask*/
#define MSC_RESP_ABORT_MASK (1<<6)
#define MSC_REQ_ABORT_MASK (1<<5)
#define MSC_REQ_DONE_MASK (1<<4)
#define MSC_MSG_RECD_MASK (1<<3)
#define CBUS_DDC_ABORT_MASK (1<<2)
/* CBUS INTR2 STATUS Register bits */
#define WRT_STAT_RECD (1<<3)
#define SET_INT_RECD (1<<2)
#define WRT_BURST_RECD (1<<0)
/* CBUS INTR2 STATUS 0x1F Enable Mask*/
#define WRT_STAT_RECD_MASK (1<<3)
#define SET_INT_RECD_MASK (1<<2)
#define WRT_BURST_RECD_MASK (1<<0)
/* CBUS Control Registers*/
/* Retry count for all MSC commands*/
#define MSC_RETRY_FAIL_LIM_REG 0x1D
/* reason for MSC_REQ_ABORT interrupt on CBUS */
#define MSC_REQ_ABORT_REASON_REG 0x0D
#define MSC_RESP_ABORT_REASON_REG 0x0E
/* MSC Requestor Abort Reason Register bits*/
#define ABORT_BY_PEER (1<<7)
#define UNDEF_CMD (1<<3)
#define TIMEOUT (1<<2)
#define PROTO_ERROR (1<<1)
#define MAX_FAIL (1<<0)
/* MSC Responder Abort Reason Register bits*/
#define ABORT_BY_PEER (1<<7)
#define UNDEF_CMD (1<<3)
#define TIMEOUT (1<<2)
/* Set HPD came from Downstream, not documented */
#define SET_HPD_DOWNSTREAM (1<<6)
/* MHL TX DISC1 Register Bits */
#define DISC_EN (1<<0)
/* MHL TX DISC2 Register Bits */
#define SKIP_GND (1<<6)
#define ATT_THRESH_SHIFT 0x04
#define ATT_THRESH_MASK (0x03 << ATT_THRESH_SHIFT)
#define USB_D_OEN (1<<3)
#define DEGLITCH_TIME_MASK 0x07
#define DEGLITCH_TIME_2MS 0
#define DEGLITCH_TIME_4MS 1
#define DEGLITCH_TIME_8MS 2
#define DEGLITCH_TIME_16MS 3
#define DEGLITCH_TIME_40MS 4
#define DEGLITCH_TIME_50MS 5
#define DEGLITCH_TIME_60MS 6
#define DEGLITCH_TIME_128MS 7
#define DISC_CTRL3_COMM_IMME (1<<7)
#define DISC_CTRL3_FORCE_MHL (1<<6)
#define DISC_CTRL3_FORCE_USB (1<<4)
#define DISC_CTRL3_USB_EN (1<<3)
/* MHL TX DISC4 0x93 Register Bits: undocumented */
#define CBUS_DISC_PUP_SEL_SHIFT 6
#define CBUS_DISC_PUP_SEL_MASK (3<<CBUS_DISC_PUP_SEL_SHIFT)
#define CBUS_DISC_PUP_SEL_10K (2<<CBUS_DISC_PUP_SEL_SHIFT)
#define CBUS_DISC_PUP_SEL_OPEN (0<<CBUS_DISC_PUP_SEL_SHIFT)
#define CBUS_IDLE_PUP_SEL_SHIFT 4
#define CBUS_IDLE_PUP_SEL_MASK (3<<CBUS_IDLE_PUP_SEL_SHIFT)
#define CBUS_IDLE_PUP_SEL_OPEN (0<<CBUS_IDLE_PUP_SEL_SHIFT)
/* MHL TX DISC5 0x94 Register Bits */
#define CBUS_MHL_PUP_SEL_MASK 0x03 /* Not Documented */
#define CBUS_MHL_PUP_SEL_5K 0x01 /* Not Documented */
#define CBUS_MHL_PUP_SEL_OPEN 0x00
/* MHL TX DISC6 0x95 Register Bits */
#define USB_D_OVR (1<<7)
#define USB_ID_OVR (1<<6)
#define DVRFLT_SEL (1<<5)
#define BLOCK_RGND_INT (1<<4)
#define SKIP_DEG (1<<3)
#define CI2CA_POL (1<<2)
#define CI2CA_WKUP (1<<1)
#define SINGLE_ATT (1<<0)
/* MHL TX DISC7 0x96 Register Bits
*
* Bits 7 and 6 are labeled as reserved but seem to be related to toggling
* the CBUS signal when generating the wake pulse sequence.
*/
#define USB_D_ODN (1<<5)
#define VBUS_CHECK (1<<2)
#define RGND_INTP_MASK 0x03
#define RGND_INTP_OPEN 0
#define RGND_INTP_2K 1
#define RGND_INTP_1K 2
#define RGND_INTP_SHORT 3
/* TPI Addr 0x7A Registers */
#define TPI_DPD_REG 0x3D
#define TPI_PD_TMDS (1<<5)
#define TPI_PD_OSC_EN (1<<4)
#define TPI_TCLK_PHASE (1<<3)
#define TPI_PD_IDCK (1<<2)
#define TPI_PD_OSC (1<<1)
#define TPI_PD (1<<0)
/* HDMI RX Registers */
#define HDMI_RX_TMDS0_CCTRL1_REG 0x10
#define HDMI_RX_TMDS_CLK_EN_REG 0x11
#define HDMI_RX_TMDS_CH_EN_REG 0x12
#define HDMI_RX_PLL_CALREFSEL_REG 0x17
#define HDMI_RX_PLL_VCOCAL_REG 0x1A
#define HDMI_RX_EQ_DATA0_REG 0x22
#define HDMI_RX_EQ_DATA1_REG 0x23
#define HDMI_RX_EQ_DATA2_REG 0x24
#define HDMI_RX_EQ_DATA3_REG 0x25
#define HDMI_RX_EQ_DATA4_REG 0x26
#define HDMI_RX_TMDS_ZONE_CTRL_REG 0x4C
#define HDMI_RX_TMDS_MODE_CTRL_REG 0x4D
enum rgnd_state {
RGND_UNKNOWN = 0,
RGND_OPEN,
RGND_1K,
RGND_2K,
RGND_SHORT
};
enum mhl_state {
STATE_DISCONNECTED = 0,
STATE_DISCOVERY_FAILED,
STATE_CBUS_LOCKOUT,
STATE_ESTABLISHED,
STATE_DISCONNECTING,
};
enum cbus_command {
IDLE = 0x00,
ACK = 0x33,
NACK = 0x34,
ABORT = 0x35,
WRITE_STAT = 0x60 | 0x80,
SET_INT = 0x60,
READ_DEVCAP = 0x61,
GET_STATE = 0x62,
GET_VENDOR_ID = 0x63,
SET_HPD = 0x64,
CLR_HPD = 0x65,
SET_CAP_ID = 0x66,
GET_CAP_ID = 0x67,
MSC_MSG = 0x68,
GET_SC1_ERR_CODE = 0x69,
GET_DDC_ERR_CODE = 0x6A,
GET_MSC_ERR_CODE = 0x6B,
WRITE_BURST = 0x6C,
GET_SC3_ERR_CODE = 0x6D,
};
enum msc_subcommand {
/* MSC_MSG Sub-Command codes */
MSG_RCP = 0x10,
MSG_RCPK = 0x11,
MSG_RCPE = 0x12,
MSG_RAP = 0x20,
MSG_RAPK = 0x21,
};
static inline bool mhl_state_is_error(enum mhl_state state)
{
return state == STATE_DISCOVERY_FAILED ||
state == STATE_CBUS_LOCKOUT;
}
struct msc_data {
enum cbus_command cmd; /* cbus command type */
u8 offset; /* for MSC_MSG,stores msc_subcommand */
u8 data;
struct completion *cvar; /* optional completion signaled when
event is handled */
int *ret; /* optional return value */
struct list_head list;
};
static const u16 sii9234_rcp_def_keymap[] = {
KEY_SELECT,
KEY_UP,
KEY_DOWN,
KEY_LEFT,
KEY_RIGHT,
KEY_UNKNOWN, /* right-up */
KEY_UNKNOWN, /* right-down */
KEY_UNKNOWN, /* left-up */
KEY_UNKNOWN, /* left-down */
KEY_MENU,
KEY_UNKNOWN, /* setup */
KEY_UNKNOWN, /* contents */
KEY_UNKNOWN, /* favorite */
KEY_EXIT,
KEY_RESERVED, /* 0x0e */
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED, /* 0x1F */
KEY_NUMERIC_0,
KEY_NUMERIC_1,
KEY_NUMERIC_2,
KEY_NUMERIC_3,
KEY_NUMERIC_4,
KEY_NUMERIC_5,
KEY_NUMERIC_6,
KEY_NUMERIC_7,
KEY_NUMERIC_8,
KEY_NUMERIC_9,
KEY_DOT,
KEY_ENTER,
KEY_CLEAR,
KEY_RESERVED, /* 0x2D */
KEY_RESERVED,
KEY_RESERVED, /* 0x2F */
KEY_UNKNOWN, /* channel up */
KEY_UNKNOWN, /* channel down */
KEY_UNKNOWN, /* previous channel */
KEY_UNKNOWN, /* sound select */
KEY_UNKNOWN, /* input select */
KEY_UNKNOWN, /* show information */
KEY_UNKNOWN, /* help */
KEY_UNKNOWN, /* page up */
KEY_UNKNOWN, /* page down */
KEY_RESERVED, /* 0x39 */
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED, /* 0x3F */
KEY_RESERVED, /* 0x40 */
KEY_UNKNOWN, /* volume up */
KEY_UNKNOWN, /* volume down */
KEY_UNKNOWN, /* mute */
KEY_PLAY,
KEY_STOP,
KEY_PLAYPAUSE,
KEY_UNKNOWN, /* record */
KEY_REWIND,
KEY_FASTFORWARD,
KEY_UNKNOWN, /* eject */
KEY_NEXTSONG,
KEY_PREVIOUSSONG,
KEY_RESERVED, /* 0x4D */
KEY_RESERVED,
KEY_RESERVED, /* 0x4F */
KEY_UNKNOWN, /* angle */
KEY_UNKNOWN, /* subtitle */
KEY_RESERVED, /* 0x52 */
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED, /* 0x5F */
KEY_PLAY,
KEY_PAUSE,
KEY_UNKNOWN, /* record_function */
KEY_UNKNOWN, /* pause_record_function */
KEY_STOP,
KEY_UNKNOWN, /* mute_function */
KEY_UNKNOWN, /* restore_volume_function */
KEY_UNKNOWN, /* tune_function */
KEY_UNKNOWN, /* select_media_function */
KEY_RESERVED, /* 0x69 */
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED, /* 0x70 */
KEY_UNKNOWN, /* F1 */
KEY_UNKNOWN, /* F2 */
KEY_UNKNOWN, /* F3 */
KEY_UNKNOWN, /* F4 */
KEY_UNKNOWN, /* F5 */
KEY_RESERVED, /* 0x76 */
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED,
KEY_RESERVED, /* 0x7D */
KEY_VENDOR,
KEY_RESERVED, /* 0x7F */
};
#define SII9234_RCP_NUM_KEYS ARRAY_SIZE(sii9234_rcp_def_keymap)
struct sii9234_data {
struct sii9234_platform_data *pdata;
struct otg_id_notifier_block otg_id_nb;
wait_queue_head_t wq;
bool claimed;
enum mhl_state state;
enum rgnd_state rgnd;
int irq;
bool rsen;
struct mutex lock;
bool msc_ready;
struct mutex msc_lock;
struct completion msc_complete;
u8 devcap[16];
u8 link_mode;
struct work_struct msc_work;
struct list_head msc_data_list;
struct input_dev *input_dev;
struct mutex input_lock;
u16 keycode[SII9234_RCP_NUM_KEYS];
struct work_struct redetect_work;
struct workqueue_struct *redetect_wq;
};
static irqreturn_t sii9234_irq_thread(int irq, void *data);
static int mhl_tx_write_reg(struct sii9234_data *sii9234, unsigned int offset,
u8 value)
{
return i2c_smbus_write_byte_data(sii9234->pdata->mhl_tx_client, offset,
value);
}
static int mhl_tx_read_reg(struct sii9234_data *sii9234, unsigned int offset,
u8 *value)
{
int ret;
if (!value)
return -EINVAL;
ret = i2c_smbus_write_byte(sii9234->pdata->mhl_tx_client, offset);
if (ret < 0)
return ret;
ret = i2c_smbus_read_byte(sii9234->pdata->mhl_tx_client);
if (ret < 0)
return ret;
*value = ret & 0x000000FF;
return 0;
}
static int mhl_tx_set_reg(struct sii9234_data *sii9234, unsigned int offset,
u8 mask)
{
int ret;
u8 value;
ret = mhl_tx_read_reg(sii9234, offset, &value);
if (ret < 0)
return ret;
value |= mask;
return mhl_tx_write_reg(sii9234, offset, value);
}
static int mhl_tx_clear_reg(struct sii9234_data *sii9234, unsigned int offset,
u8 mask)
{
int ret;
u8 value;
ret = mhl_tx_read_reg(sii9234, offset, &value);
if (ret < 0)
return ret;
value &= ~mask;
return mhl_tx_write_reg(sii9234, offset, value);
}
static int tpi_write_reg(struct sii9234_data *sii9234, unsigned int offset,
u8 value)
{
return i2c_smbus_write_byte_data(sii9234->pdata->tpi_client, offset,
value);
}
static int tpi_read_reg(struct sii9234_data *sii9234, unsigned int offset,
u8 *value)
{
int ret;
if (!value)
return -EINVAL;
ret = i2c_smbus_write_byte(sii9234->pdata->tpi_client, offset);
if (ret < 0)
return ret;
ret = i2c_smbus_read_byte(sii9234->pdata->tpi_client);
if (ret < 0)
return ret;
*value = ret & 0x000000FF;
return 0;
}
static int hdmi_rx_write_reg(struct sii9234_data *sii9234, unsigned int offset,
u8 value)
{
return i2c_smbus_write_byte_data(sii9234->pdata->hdmi_rx_client, offset,
value);
}
static int cbus_write_reg(struct sii9234_data *sii9234, unsigned int offset,
u8 value)
{
return i2c_smbus_write_byte_data(sii9234->pdata->cbus_client, offset,
value);
}
static int cbus_read_reg(struct sii9234_data *sii9234, unsigned int offset,
u8 *value)
{
int ret;
if (!value)
return -EINVAL;
ret = i2c_smbus_write_byte(sii9234->pdata->cbus_client, offset);
if (ret < 0)
return ret;
ret = i2c_smbus_read_byte(sii9234->pdata->cbus_client);
if (ret < 0)
return ret;
*value = ret & 0x000000FF;
return 0;
}
static int cbus_set_reg(struct sii9234_data *sii9234, unsigned int offset,
u8 mask)
{
int ret;
u8 value;
ret = cbus_read_reg(sii9234, offset, &value);
if (ret < 0)
return ret;
value |= mask;
return cbus_write_reg(sii9234, offset, value);
}
static int mhl_wake_toggle(struct sii9234_data *sii9234,
unsigned long high_period,
unsigned long low_period)
{
int ret;
/* These bits are not documented. */
ret = mhl_tx_set_reg(sii9234, MHL_TX_DISC_CTRL7_REG, (1<<7) | (1<<6));
if (ret < 0)
return ret;
usleep_range(high_period * USEC_PER_MSEC, high_period * USEC_PER_MSEC);
ret = mhl_tx_clear_reg(sii9234, MHL_TX_DISC_CTRL7_REG, (1<<7) | (1<<6));
if (ret < 0)
return ret;
usleep_range(low_period * USEC_PER_MSEC, low_period * USEC_PER_MSEC);
return 0;
}
static int mhl_send_wake_pulses(struct sii9234_data *sii9234)
{
int ret;
ret = mhl_wake_toggle(sii9234, T_SRC_WAKE_PULSE_WIDTH_1,
T_SRC_WAKE_PULSE_WIDTH_1);
if (ret < 0)
return ret;
ret = mhl_wake_toggle(sii9234, T_SRC_WAKE_PULSE_WIDTH_1,
T_SRC_WAKE_PULSE_WIDTH_2);
if (ret < 0)
return ret;
ret = mhl_wake_toggle(sii9234, T_SRC_WAKE_PULSE_WIDTH_1,
T_SRC_WAKE_PULSE_WIDTH_1);
if (ret < 0)
return ret;
ret = mhl_wake_toggle(sii9234, T_SRC_WAKE_PULSE_WIDTH_1,
T_SRC_WAKE_TO_DISCOVER);
if (ret < 0)
return ret;
return 0;
}
static int sii9234_cbus_reset(struct sii9234_data *sii9234)
{
int ret;
/* Reset CBUS */
ret = mhl_tx_set_reg(sii9234, MHL_TX_SRST, 0x03);
if (ret < 0)
return ret;
usleep_range(2000, 3000);
ret = mhl_tx_clear_reg(sii9234, MHL_TX_SRST, 0x03);
if (ret < 0)
return ret;
/* Adjust interrupt mask everytime reset is performed.*/
ret = cbus_write_reg(sii9234,
CBUS_INT_1_MASK,
MSC_RESP_ABORT_MASK |
MSC_REQ_ABORT_MASK |
MSC_REQ_DONE_MASK |
MSC_MSG_RECD_MASK |
CBUS_DDC_ABORT_MASK);
if (ret < 0)
return ret;
ret = cbus_write_reg(sii9234,
CBUS_INT_2_MASK,
WRT_STAT_RECD_MASK |
SET_INT_RECD_MASK);
if (ret < 0)
return ret;
return 0;
}
static int sii9234_cbus_init(struct sii9234_data *sii9234)
{
u8 value;
cbus_write_reg(sii9234, 0x07, 0x32);
cbus_write_reg(sii9234, 0x40, 0x03);
cbus_write_reg(sii9234, 0x42, 0x06);
cbus_write_reg(sii9234, 0x36, 0x0C);
cbus_write_reg(sii9234, 0x3D, 0xFD);
cbus_write_reg(sii9234, 0x1C, 0x00);
cbus_write_reg(sii9234, 0x44, 0x02);
/* Setup our devcap*/
cbus_write_reg(sii9234, 0x80, 0x04);
cbus_write_reg(sii9234, 0x81, 0x10);
cbus_write_reg(sii9234, 0x82, 0x02);
cbus_write_reg(sii9234, 0x83, 0);
cbus_write_reg(sii9234, 0x84, 0);
cbus_write_reg(sii9234, 0x85, 0x01 | 0x02);
cbus_write_reg(sii9234, 0x86, 0x01);
cbus_write_reg(sii9234, 0x87, 0);
cbus_write_reg(sii9234, 0x88, (1<<2) | (1<<1) | (1<<3) | (1<<7));
cbus_write_reg(sii9234, 0x89, 0x0F);
cbus_write_reg(sii9234, 0x8A, (1<<0) | (1<<1) | (1<<2));
cbus_write_reg(sii9234, 0x8B, 0);
cbus_write_reg(sii9234, 0x8C, 0);
cbus_write_reg(sii9234, 0x8D, 16);
cbus_write_reg(sii9234, 0x8E, 0x44);
cbus_write_reg(sii9234, 0x8F, 0);
cbus_read_reg(sii9234, 0x31, &value);
value |= 0x0C;
cbus_write_reg(sii9234, 0x31, value);
cbus_read_reg(sii9234, 0x22, &value);
value &= 0x0F;
cbus_write_reg(sii9234, 0x22, value);
cbus_write_reg(sii9234, 0x30, 0x01);
return 0;
}
static int sii9234_power_init(struct sii9234_data *sii9234)
{
int ret;
/* Force the SiI9234 into the D0 state. */
ret = tpi_write_reg(sii9234, TPI_DPD_REG, 0x3F);
if (ret < 0)
return ret;
/* Enable TxPLL Clock */
ret = hdmi_rx_write_reg(sii9234, HDMI_RX_TMDS_CLK_EN_REG, 0x01);
if (ret < 0)
return ret;
/* Enable Tx Clock Path & Equalizer*/
ret = hdmi_rx_write_reg(sii9234, HDMI_RX_TMDS_CH_EN_REG, 0x15);
if (ret < 0)
return ret;
/* Power Up TMDS*/
ret = mhl_tx_write_reg(sii9234, 0x08, 0x35);
if (ret < 0)
return ret;
return 0;
}
static void sii9234_hdmi_init(struct sii9234_data *sii9234)
{
/* Analog PLL Control
* bits 5:4 = 2b00 as per characterization team.
*/
hdmi_rx_write_reg(sii9234, HDMI_RX_TMDS0_CCTRL1_REG, 0xC1);
/* PLL Calrefsel */
hdmi_rx_write_reg(sii9234, HDMI_RX_PLL_CALREFSEL_REG, 0x03);
/* VCO Cal */
hdmi_rx_write_reg(sii9234, HDMI_RX_PLL_VCOCAL_REG, 0x20);
/* Auto EQ */
hdmi_rx_write_reg(sii9234, HDMI_RX_EQ_DATA0_REG, 0x8A);
/* Auto EQ */
hdmi_rx_write_reg(sii9234, HDMI_RX_EQ_DATA1_REG, 0x6A);
/* Auto EQ */
hdmi_rx_write_reg(sii9234, HDMI_RX_EQ_DATA2_REG, 0xAA);
/* Auto EQ */
hdmi_rx_write_reg(sii9234, HDMI_RX_EQ_DATA3_REG, 0xCA);
/* Auto EQ */
hdmi_rx_write_reg(sii9234, HDMI_RX_EQ_DATA4_REG, 0xEA);
/* Manual zone */
hdmi_rx_write_reg(sii9234, HDMI_RX_TMDS_ZONE_CTRL_REG, 0xA0);
/* PLL Mode Value */
hdmi_rx_write_reg(sii9234, HDMI_RX_TMDS_MODE_CTRL_REG, 0x00);
mhl_tx_write_reg(sii9234, MHL_TX_TMDS_CCTRL, 0x34);
hdmi_rx_write_reg(sii9234, 0x45, 0x44);
/* Rx PLL BW ~ 4MHz */
hdmi_rx_write_reg(sii9234, 0x31, 0x0A);
/* Analog PLL Control
* bits 5:4 = 2b00 as per characterization team.
*/
hdmi_rx_write_reg(sii9234, HDMI_RX_TMDS0_CCTRL1_REG, 0xC1);
}
static int sii9234_register_input_device(struct sii9234_data *sii9234)
{
struct input_dev *input;
int ret;
u8 i;
input = input_allocate_device();
if (!input) {
pr_err("sii9234: failed to allocate input device\n");
return -ENOMEM;
}
/* indicate that we generate key events */
set_bit(EV_KEY, input->evbit);
memcpy(sii9234->keycode, sii9234_rcp_def_keymap,
SII9234_RCP_NUM_KEYS *
sizeof(sii9234_rcp_def_keymap[0]));
input->keycode = sii9234->keycode;
input->keycodemax = SII9234_RCP_NUM_KEYS;
input->keycodesize = sizeof(sii9234->keycode[0]);
for (i = 0; i < SII9234_RCP_NUM_KEYS; i++) {
u16 keycode = sii9234->keycode[i];
if (keycode != KEY_UNKNOWN && keycode != KEY_RESERVED)
set_bit(keycode, input->keybit);
}
input_set_drvdata(input, sii9234);
input->name = "sii9234_rcp";
input->id.bustype = BUS_I2C;
pr_debug("sii9234: registering input device\n");
ret = input_register_device(input);
if (ret < 0) {
pr_err("sii9234: failed to register input device\n");
input_free_device(input);
return ret;
}
mutex_lock(&sii9234->input_lock);
sii9234->input_dev = input;
mutex_unlock(&sii9234->input_lock);
return 0;
}
static void sii9234_unregister_input_device(struct sii9234_data *sii9234)
{
mutex_lock(&sii9234->input_lock);
if (sii9234->input_dev) {
pr_debug("sii9234: unregistering input device\n");
input_unregister_device(sii9234->input_dev);
sii9234->input_dev = NULL;
}
mutex_unlock(&sii9234->input_lock);
}
static void sii9234_mhl_tx_ctl_int(struct sii9234_data *sii9234)
{
mhl_tx_write_reg(sii9234, MHL_TX_MHLTX_CTL1_REG, 0xD0);
mhl_tx_write_reg(sii9234, MHL_TX_MHLTX_CTL2_REG, 0xFC);
mhl_tx_write_reg(sii9234, MHL_TX_MHLTX_CTL4_REG, 0xEB);
mhl_tx_write_reg(sii9234, MHL_TX_MHLTX_CTL7_REG, 0x0C);
}
static void sii9234_power_down(struct sii9234_data *sii9234)
{
if (sii9234->claimed)
sii9234->pdata->connect(false, NULL);
sii9234_unregister_input_device(sii9234);
sii9234->state = STATE_DISCONNECTED;
sii9234->claimed = false;
tpi_write_reg(sii9234, TPI_DPD_REG, 0);
sii9234->pdata->power(0);
sii9234->pdata->enable(0);
}
/* toggle hpd line low for 100ms */
static void sii9234_toggle_hpd(struct sii9234_data *sii9234)
{
mhl_tx_set_reg(sii9234, MHL_TX_INT_CTRL_REG, HPD_OUT_OVR_EN);
mhl_tx_clear_reg(sii9234, MHL_TX_INT_CTRL_REG, HPD_OUT_OVR_VAL);
msleep(100);
mhl_tx_set_reg(sii9234, MHL_TX_INT_CTRL_REG, HPD_OUT_OVR_VAL);
mhl_tx_clear_reg(sii9234, MHL_TX_INT_CTRL_REG, HPD_OUT_OVR_EN);
}
/* Must call with sii9234->lock held */
static int sii9234_msc_req_locked(struct sii9234_data *sii9234, u8 req_type,
u8 offset, u8 first_data, u8 second_data)
{
int ret;
bool write_offset = req_type &
(START_READ_DEVCAP | START_WRITE_STAT_INT | START_WRITE_BURST);
bool write_first_data = req_type &
(START_WRITE_STAT_INT | START_MSC_MSG);
bool write_second_data = req_type & START_MSC_MSG;
if (sii9234->state != STATE_ESTABLISHED)
return -ENOENT;
mutex_unlock(&sii9234->lock);
ret = wait_event_timeout(sii9234->wq, sii9234->msc_ready,
msecs_to_jiffies(2000));
mutex_lock(&sii9234->lock);
if (!sii9234->msc_ready)
return -EIO;
init_completion(&sii9234->msc_complete);
if (write_offset)
cbus_write_reg(sii9234, CBUS_MSC_OFFSET_REG, offset);
if (write_first_data)
cbus_write_reg(sii9234, CBUS_MSC_FIRST_DATA_OUT, first_data);
if (write_second_data)
cbus_write_reg(sii9234, CBUS_MSC_SECOND_DATA_OUT, second_data);
cbus_write_reg(sii9234, CBUS_MSC_COMMAND_START, req_type);
mutex_unlock(&sii9234->lock);
ret = wait_for_completion_timeout(&sii9234->msc_complete,
msecs_to_jiffies(500));
mutex_lock(&sii9234->lock);
return ret ? 0 : -EIO;
}
/* Must call with sii9234->lock held */
static int sii9234_devcap_read_locked(struct sii9234_data *sii9234, u8 offset)
{
int ret;
u8 val;
if (offset > 0xf)
return -EINVAL;
ret = sii9234_msc_req_locked(sii9234, START_READ_DEVCAP, offset, 0, 0);
if (ret < 0)
return ret;
ret = cbus_read_reg(sii9234, CBUS_MSC_FIRST_DATA_IN, &val);
if (ret < 0)
return ret;
return val;
}
static int sii9234_queue_devcap_read_locked(struct sii9234_data *sii9234,
u8 offset)
{
struct completion cvar;
struct msc_data *data;
int ret;
data = kzalloc(sizeof(struct msc_data), GFP_KERNEL);
if (!data) {
dev_err(&sii9234->pdata->mhl_tx_client->dev,
"failed to allocate msc data");
return -ENOMEM;
}
init_completion(&cvar);
data->cmd = READ_DEVCAP;
data->offset = offset;
data->cvar = &cvar;
data->ret = &ret;
list_add_tail(&data->list, &sii9234->msc_data_list);
mutex_unlock(&sii9234->lock);
schedule_work(&sii9234->msc_work);
wait_for_completion(&cvar);
mutex_lock(&sii9234->lock);
return ret;
}
static int sii9234_detection_callback(struct otg_id_notifier_block *nb)
{
struct sii9234_data *sii9234 = container_of(nb, struct sii9234_data,
otg_id_nb);
int ret;
int i;
u8 value;
int handled = OTG_ID_UNHANDLED;
pr_debug("si9234: detection started\n");
mutex_lock(&sii9234->lock);
sii9234->link_mode = MHL_STATUS_CLK_NORMAL;
sii9234->rgnd = RGND_UNKNOWN;
sii9234->rsen = false;
sii9234->msc_ready = false;
if (sii9234->state == STATE_DISCONNECTING) {
pr_debug("sii9234: disconnecting, bypassing detection\n");
sii9234->state = STATE_DISCONNECTED;
mutex_unlock(&sii9234->lock);
return OTG_ID_UNHANDLED;
}
sii9234->state = STATE_DISCONNECTED;
/* Set the board configuration so the SiI9234 has access to the
* external connector.
*/
sii9234->pdata->enable(1);
sii9234->pdata->power(1);
ret = sii9234_power_init(sii9234);
if (ret < 0)
goto unhandled;
sii9234_hdmi_init(sii9234);
sii9234_mhl_tx_ctl_int(sii9234);
/* Enable HDCP Compliance safety*/
ret = mhl_tx_write_reg(sii9234, 0x2B, 0x01);
if (ret < 0)
goto unhandled;
/* CBUS discovery cycle time for each drive and float = 150us*/
ret = mhl_tx_read_reg(sii9234, MHL_TX_DISC_CTRL1_REG, &value);
if (ret < 0)
goto unhandled;
value &= ~(1<<2);
value |= (1<<3);
ret = mhl_tx_write_reg(sii9234, MHL_TX_DISC_CTRL1_REG, value);
if (ret < 0)
goto unhandled;
/* Clear bit 6 (reg_skip_rgnd) */
ret = mhl_tx_write_reg(sii9234, MHL_TX_DISC_CTRL2_REG,
(1<<7) /* Reserved Bit */ |
2 << ATT_THRESH_SHIFT |
DEGLITCH_TIME_128MS);
if (ret < 0)
goto unhandled;
/* Changed from 66 to 65 for 94[1:0] = 01 = 5k reg_cbusmhl_pup_sel */
/* 1.8V CBUS VTH & GND threshold */
ret = mhl_tx_write_reg(sii9234, MHL_TX_DISC_CTRL5_REG, 0x75);
if (ret < 0)
goto unhandled;
/* set bit 2 and 3, which is Initiator Timeout */
ret = cbus_read_reg(sii9234, CBUS_LINK_CONTROL_2_REG, &value);
if (ret < 0)
goto unhandled;
value |= 0x0C;
ret = cbus_write_reg(sii9234, CBUS_LINK_CONTROL_2_REG, value);
if (ret < 0)
goto unhandled;
ret = mhl_tx_write_reg(sii9234, MHL_TX_MHLTX_CTL6_REG, 0xA0);
if (ret < 0)
goto unhandled;
/* RGND & single discovery attempt (RGND blocking) */
ret = mhl_tx_write_reg(sii9234, MHL_TX_DISC_CTRL6_REG, BLOCK_RGND_INT |
DVRFLT_SEL | SINGLE_ATT);
if (ret < 0)
goto unhandled;
/* Use VBUS path of discovery state machine*/
ret = mhl_tx_write_reg(sii9234, MHL_TX_DISC_CTRL8_REG, 0);
if (ret < 0)
goto unhandled;
ret = mhl_tx_set_reg(sii9234, MHL_TX_DISC_CTRL6_REG, USB_ID_OVR);
if (ret < 0)
goto unhandled;
/* To allow RGND engine to operate correctly.
* When moving the chip from D2 to D0 (power up, init regs)
* the values should be
* 94[1:0] = 01 reg_cbusmhl_pup_sel[1:0] should be set for 5k
* 93[7:6] = 10 reg_cbusdisc_pup_sel[1:0] should be
* set for 10k (default)
* 93[5:4] = 00 reg_cbusidle_pup_sel[1:0] = open (default)
*/
ret = mhl_tx_set_reg(sii9234, MHL_TX_DISC_CTRL3_REG, 0x86);
if (ret < 0)
goto unhandled;
/* change from CC to 8C to match 5K*/
ret = mhl_tx_set_reg(sii9234, MHL_TX_DISC_CTRL4_REG, 0x8C);
if (ret < 0)
goto unhandled;
/* Configure the interrupt as active high */
ret = mhl_tx_clear_reg(sii9234, MHL_TX_INT_CTRL_REG, (1<<2) | (1<<1));
if (ret < 0)
goto unhandled;
msleep(25);
ret = mhl_tx_clear_reg(sii9234, MHL_TX_DISC_CTRL6_REG, USB_ID_OVR);
if (ret < 0)
goto unhandled;
ret = mhl_tx_write_reg(sii9234, MHL_TX_DISC_CTRL1_REG, 0x27);
if (ret < 0)
goto unhandled;
/* Reset CBUS */
ret = sii9234_cbus_reset(sii9234);
if (ret < 0)
goto unhandled;
sii9234_cbus_init(sii9234);
/* Enable Auto soft reset on SCDT = 0*/
ret = mhl_tx_write_reg(sii9234, 0x05, 0x04);
if (ret < 0)
goto unhandled;
/* HDMI Transcode mode enable*/
ret = mhl_tx_write_reg(sii9234, 0x0D, 0x1C);
if (ret < 0)
goto unhandled;
ret = mhl_tx_write_reg(sii9234, MHL_TX_INTR4_ENABLE_REG,
RGND_READY_MASK | CBUS_LKOUT_MASK |
MHL_DISC_FAIL_MASK | MHL_EST_MASK);
if (ret < 0)
goto unhandled;
ret = mhl_tx_write_reg(sii9234, MHL_TX_INTR1_ENABLE_REG,
(1<<5) | (1<<6));
if (ret < 0)
goto unhandled;
pr_debug("sii9234: waiting for RGND measurement\n");
enable_irq(sii9234->irq);
/* SiI9244 Programmer's Reference Section 2.4.3
* State : RGND Ready
*/
mutex_unlock(&sii9234->lock);
ret = wait_event_timeout(sii9234->wq,
((sii9234->rgnd != RGND_UNKNOWN) ||
mhl_state_is_error(sii9234->state)),
msecs_to_jiffies(2000));
mutex_lock(&sii9234->lock);
if (sii9234->rgnd == RGND_UNKNOWN || mhl_state_is_error(sii9234->state))
goto unhandled;
if (sii9234->rgnd != RGND_1K)
goto unhandled;
mutex_unlock(&sii9234->lock);
pr_debug("sii9234: waiting for detection\n");
ret = wait_event_timeout(sii9234->wq,
sii9234->state != STATE_DISCONNECTED,
msecs_to_jiffies(500));
mutex_lock(&sii9234->lock);
if (sii9234->state == STATE_DISCONNECTED)
goto unhandled;
if (sii9234->state == STATE_DISCOVERY_FAILED) {
handled = OTG_ID_PROXY_WAIT;
goto unhandled;
}
if (mhl_state_is_error(sii9234->state))
goto unhandled;
mutex_unlock(&sii9234->lock);
wait_event_timeout(sii9234->wq, sii9234->rsen,
msecs_to_jiffies(T_WAIT_TIMEOUT_RSEN_INT));
mutex_lock(&sii9234->lock);
if (!sii9234->rsen) {
ret = mhl_tx_read_reg(sii9234, MHL_TX_SYSSTAT_REG, &value);
pr_debug("sii9234: Recheck RSEN value\n");
if (!(ret && (value & RSEN_STATUS))) {
usleep_range(T_SRC_RXSENSE_DEGLITCH * USEC_PER_MSEC,
T_SRC_RXSENSE_DEGLITCH * USEC_PER_MSEC);
pr_debug("sii9234: RSEN is low -> retry once\n");
ret = mhl_tx_read_reg(sii9234, MHL_TX_SYSSTAT_REG,
&value);
if (!(ret && (value & RSEN_STATUS))) {
pr_debug("sii9234: RSEN is still low\n");
goto unhandled;
}
}
sii9234->rsen = value & RSEN_STATUS;
}
memset(sii9234->devcap, 0x0, sizeof(sii9234->devcap));
for (i = 0; i < 16; i++) {
ret = sii9234_queue_devcap_read_locked(sii9234, i);
if (ret < 0)
goto unhandled;
}
#ifdef DEBUG
if (ret >= 0)
print_hex_dump(KERN_DEBUG, "sii9234: devcap = ", DUMP_PREFIX_NONE,
16, 1, sii9234->devcap, 16, false);
#endif
/* It's possible for devcap reading to fail but the adapter still
* be connected. Therefore we must keep ownership of the port
* as long as it's still connected.
*/
if (sii9234->state != STATE_ESTABLISHED)
goto unhandled;
pr_info("si9234: connection established\n");
sii9234->claimed = true;
sii9234->pdata->connect(true, ret >= 0 ? sii9234->devcap : NULL);
if (sii9234->devcap[MHL_DEVCAP_FEATURE_FLAG] &
MHL_FEATURE_FLAG_RCP_SUPPORT)
sii9234_register_input_device(sii9234);
mutex_unlock(&sii9234->lock);
return OTG_ID_HANDLED;
unhandled:
pr_info("sii9234: Detection failed");
if (sii9234->state == STATE_DISCONNECTED)
pr_cont(" (timeout)");
else if (sii9234->state == STATE_DISCOVERY_FAILED)
pr_cont(" (discovery failed)");
else if (sii9234->state == STATE_CBUS_LOCKOUT)
pr_cont(" (cbus_lockout)");
pr_cont("\n");
disable_irq_nosync(sii9234->irq);
/* MHL Specs:"A source should reattempt discovery multiple times
* for as long as the Source requirement of discovery persists".
*/
if (sii9234->state == STATE_DISCOVERY_FAILED &&
sii9234->rgnd == RGND_1K) {
sii9234->pdata->power(0);
queue_work(sii9234->redetect_wq, &sii9234->redetect_work);
handled = OTG_ID_HANDLED;
} else {
sii9234_power_down(sii9234);
}
mutex_unlock(&sii9234->lock);
return handled;
}
static void sii9234_cancel_callback(struct otg_id_notifier_block *nb)
{
struct sii9234_data *sii9234 = container_of(nb, struct sii9234_data,
otg_id_nb);
mutex_lock(&sii9234->lock);
sii9234_power_down(sii9234);
mutex_unlock(&sii9234->lock);
}
static void sii9234_retry_detection(struct work_struct *work)
{
struct sii9234_data *sii9234 = container_of(work, struct sii9234_data,
redetect_work);
pr_info("sii9234: detection restarted\n");
/* if redetection fails, notify otg to take control */
if (sii9234_detection_callback(&sii9234->otg_id_nb) == OTG_ID_UNHANDLED)
otg_id_notify();
}
static void rcp_key_report(struct sii9234_data *sii9234, u16 key)
{
pr_debug("sii9234: report rcp key: %d\n", key);
mutex_lock(&sii9234->input_lock);
if (sii9234->input_dev) {
input_report_key(sii9234->input_dev, key, 1);
input_report_key(sii9234->input_dev, key, 0);
input_sync(sii9234->input_dev);
}
mutex_unlock(&sii9234->input_lock);
}
static void cbus_process_rcp_key(struct sii9234_data *sii9234, u8 key)
{
if (key < SII9234_RCP_NUM_KEYS &&
sii9234->keycode[key] != KEY_UNKNOWN &&
sii9234->keycode[key] != KEY_RESERVED) {
/* Report the key */
rcp_key_report(sii9234, sii9234->keycode[key]);
} else {
/*
* Send a RCPE(RCP Error Message) to Peer followed by
* RCPK with old key-code so that initiator(TV) can
* recognize failed key code.error code = 0x01 means
* Ineffective key code was received.
* See Table 21.(PRM)for details.
*/
sii9234_msc_req_locked(sii9234, START_MSC_MSG,
0, MSG_RCPE, 0x01);
}
/* Send the RCP ack */
sii9234_msc_req_locked(sii9234, START_MSC_MSG, 0, MSG_RCPK, key);
}
static u8 sii9234_tmds_control(struct sii9234_data *sii9234, bool enable)
{
u8 ret = -1;
if (enable) {
ret = mhl_tx_set_reg(sii9234, MHL_TX_TMDS_CCTRL, (1<<4));
if (ret < 0)
return ret;
pr_debug("sii9234: MHL HPD High, enabled TMDS\n");
ret = mhl_tx_set_reg(sii9234, MHL_TX_INT_CTRL_REG,
(1<<4) | (1<<5));
} else {
ret = mhl_tx_clear_reg(sii9234, MHL_TX_TMDS_CCTRL, (1<<4));
if (ret < 0)
return ret;
pr_debug("sii9234 MHL HPD low, disabled TMDS\n");
ret = mhl_tx_clear_reg(sii9234, MHL_TX_INT_CTRL_REG,
(1<<4) | (1<<5));
}
return ret;
}
static void cbus_process_rap_key(struct sii9234_data *sii9234, u8 key)
{
u8 err = 0x00; /* no error */
switch (key) {
case CBUS_MSC_RAP_POLL:
/* no action, just sent to elicit an ACK */
break;
case CBUS_MSC_RAP_CONTENT_ON:
sii9234_tmds_control(sii9234, true);
break;
case CBUS_MSC_RAP_CONTENT_OFF:
sii9234_tmds_control(sii9234, false);
break;
default:
pr_debug("sii9234: unrecognized RAP code %u\n", key);
err = 0x01; /* unrecognized action code */
}
sii9234_msc_req_locked(sii9234, START_MSC_MSG, 0, MSG_RAPK, err);
}
static int cbus_handle_set_interrupt(struct sii9234_data *sii9234,
u8 offset, u8 data)
{
u8 ret = -1;
ret = sii9234_msc_req_locked(sii9234, START_WRITE_STAT_INT, offset,
data, 0);
if (ret < 0)
return ret;
if (offset == CBUS_MHL_INTR_OFFSET_0 && data == MHL_INT_DCAP_CHG) {
/* notify the peer by updating the status register too */
sii9234_msc_req_locked(sii9234, START_WRITE_STAT_INT,
CBUS_MHL_STATUS_OFFSET_0,
MHL_STATUS_DCAP_READY, 0);
}
return ret;
}
static void sii9234_msc_event(struct work_struct *work)
{
int ret = -1;
struct msc_data *data, *next;
struct sii9234_data *sii9234 = container_of(work, struct sii9234_data,
msc_work);
mutex_lock(&sii9234->msc_lock);
mutex_lock(&sii9234->lock);
list_for_each_entry_safe(data, next, &sii9234->msc_data_list, list) {
switch (data->cmd) {
case MSC_MSG:
switch (data->offset) {
case MSG_RCP:
pr_debug("sii9234: RCP Arrived. KEY CODE:%d\n",
data->data);
cbus_process_rcp_key(sii9234, data->data);
break;
case MSG_RAP:
pr_debug("sii9234: RAP Arrived\n");
cbus_process_rap_key(sii9234, data->data);
break;
case MSG_RCPK:
pr_debug("sii9234: RCPK Arrived\n");
break;
case MSG_RCPE:
pr_debug("sii9234: RCPE Arrived\n");
break;
case MSG_RAPK:
pr_debug("sii9234: RAPK Arrived\n");
break;
default:
pr_debug("sii9234: MAC error\n");
break;
}
break;
case READ_DEVCAP:
ret = sii9234_devcap_read_locked(sii9234, data->offset);
if (ret < 0) {
pr_err("sii9234: error reading device capability"
"register:%d", data->offset);
break;
}
sii9234->devcap[data->offset] = ret;
ret = 0;
break;
case SET_INT:
ret = cbus_handle_set_interrupt(sii9234, data->offset,
data->data);
if (ret < 0)
pr_err("sii9234: error requesting set_int\n");
break;
case WRITE_STAT:
ret = sii9234_msc_req_locked(sii9234,
START_WRITE_STAT_INT, data->offset,
data->data, 0);
if (ret < 0)
pr_err("sii9234: error requesting write_stat\n");
break;
case WRITE_BURST:
/* TODO: */
break;
case GET_STATE:
case GET_VENDOR_ID:
case SET_HPD:
case CLR_HPD:
case GET_MSC_ERR_CODE:
case GET_SC3_ERR_CODE:
case GET_SC1_ERR_CODE:
case GET_DDC_ERR_CODE:
ret = sii9234_msc_req_locked(sii9234,
START_MSC_RESERVED, data->offset,
data->data, 0);
if (ret < 0)
pr_err("sii9234: error requesting offset:%d"
"data:%d", data->offset, data->data);
break;
default:
pr_info("sii9234: invalid msc command\n");
break;
}
if (data->cvar) {
*data->ret = ret;
complete(data->cvar);
}
list_del(&data->list);
kfree(data);
}
mutex_unlock(&sii9234->lock);
mutex_unlock(&sii9234->msc_lock);
}
static void cbus_resp_abort_error(struct sii9234_data *sii9234)
{
u8 abort_reason = 0;
pr_debug("sii9234: MSC Response Aborted:");
cbus_read_reg(sii9234, MSC_RESP_ABORT_REASON_REG, &abort_reason);
cbus_write_reg(sii9234, MSC_RESP_ABORT_REASON_REG, 0xFF);
if (abort_reason) {
if (abort_reason & ABORT_BY_PEER)
pr_cont(" Peer Sent an ABORT");
if (abort_reason & UNDEF_CMD)
pr_cont(" Undefined Opcode");
if (abort_reason & TIMEOUT)
pr_cont(" Requestor Translation layer Timeout");
}
pr_cont("\n");
}
static void cbus_req_abort_error(struct sii9234_data *sii9234)
{
u8 abort_reason = 0;
pr_debug("sii9234: MSC Request Aborted:");
cbus_read_reg(sii9234, MSC_REQ_ABORT_REASON_REG, &abort_reason);
cbus_write_reg(sii9234, MSC_REQ_ABORT_REASON_REG, 0xFF);
if (abort_reason) {
if (abort_reason & ABORT_BY_PEER)
pr_cont(" Peer Sent an ABORT");
if (abort_reason & UNDEF_CMD)
pr_cont(" Undefined Opcode");
if (abort_reason & TIMEOUT)
pr_cont(" Requestor Translation layer Timeout");
if (abort_reason & MAX_FAIL) {
u8 msc_retry_thr_val = 0;
pr_cont(" Retry Threshold exceeded");
cbus_read_reg(sii9234,
MSC_RETRY_FAIL_LIM_REG,
&msc_retry_thr_val);
pr_cont("Retry Threshold value is:%d",
msc_retry_thr_val);
}
}
pr_cont("\n");
}
static void force_usb_id_switch_open(struct sii9234_data *sii9234)
{
pr_debug("sii9234: open usb_id\n");
/*Disable CBUS discovery*/
mhl_tx_clear_reg(sii9234, MHL_TX_DISC_CTRL1_REG, (1<<0));
/*Force USB ID switch to open*/
mhl_tx_set_reg(sii9234, MHL_TX_DISC_CTRL6_REG, USB_ID_OVR);
mhl_tx_set_reg(sii9234, MHL_TX_DISC_CTRL3_REG, 0xA6);
/*Force upstream HPD to 0 when not in MHL mode.*/
mhl_tx_clear_reg(sii9234, MHL_TX_INT_CTRL_REG, (1<<5));
mhl_tx_set_reg(sii9234, MHL_TX_INT_CTRL_REG, (1<<4));
}
static void release_usb_id_switch_open(struct sii9234_data *sii9234)
{
usleep_range(T_SRC_CBUS_FLOAT * USEC_PER_MSEC,
T_SRC_CBUS_FLOAT * USEC_PER_MSEC);
pr_debug("sii9234: release usb_id\n");
/* clear USB ID switch to open*/
mhl_tx_clear_reg(sii9234, MHL_TX_DISC_CTRL6_REG, USB_ID_OVR);
/* Enable CBUS discovery*/
mhl_tx_set_reg(sii9234, MHL_TX_DISC_CTRL1_REG, (1<<0));
}
static bool cbus_ddc_abort_error(struct sii9234_data *sii9234)
{
u8 val1, val2;
/* clear the ddc abort counter */
cbus_write_reg(sii9234, 0x29, 0xFF);
cbus_read_reg(sii9234, 0x29, &val1);
usleep_range(3000, 4000);
cbus_read_reg(sii9234, 0x29, &val2);
if (val2 > val1 + 50) {
pr_debug("Applying DDC Abort Safety(SWA 18958)\n)");
mhl_tx_set_reg(sii9234, MHL_TX_SRST, (1<<3));
mhl_tx_clear_reg(sii9234, MHL_TX_SRST, (1<<3));
force_usb_id_switch_open(sii9234);
release_usb_id_switch_open(sii9234);
mhl_tx_write_reg(sii9234, MHL_TX_MHLTX_CTL1_REG, 0xD0);
sii9234_tmds_control(sii9234, false);
/* Disconnect and notify to OTG */
return true;
}
pr_debug("sii9234: DDC abort interrupt\n");
return false;
}
static int sii9234_cbus_irq(struct sii9234_data *sii9234)
{
u8 cbus_intr1, cbus_intr2;
u8 mhl_intr0, mhl_intr1;
u8 mhl_status0, mhl_status1, mhl_status2, mhl_status3;
int ret = 0;
cbus_read_reg(sii9234, CBUS_INT_STATUS_1_REG, &cbus_intr1);
cbus_read_reg(sii9234, CBUS_INT_STATUS_2_REG, &cbus_intr2);
cbus_read_reg(sii9234, CBUS_MHL_INTR_REG_0, &mhl_intr0);
cbus_read_reg(sii9234, CBUS_MHL_INTR_REG_1, &mhl_intr1);
cbus_read_reg(sii9234, CBUS_MHL_STATUS_REG_0, &mhl_status0);
cbus_read_reg(sii9234, CBUS_MHL_STATUS_REG_1, &mhl_status1);
cbus_read_reg(sii9234, CBUS_MHL_STATUS_REG_2, &mhl_status2);
cbus_read_reg(sii9234, CBUS_MHL_STATUS_REG_3, &mhl_status3);
pr_debug("sii9234: cbus_intr %02x %02x\n", cbus_intr1, cbus_intr2);
if (cbus_intr1 & MSC_RESP_ABORT)
cbus_resp_abort_error(sii9234);
if (cbus_intr1 & MSC_REQ_ABORT)
cbus_req_abort_error(sii9234);
if (cbus_intr1 & CBUS_DDC_ABORT) {
pr_warn("sii9234: ddc abort\n");
if (cbus_ddc_abort_error(sii9234)) {
/* error on ddc line,should it be -EIO? */
ret = -EINVAL;
goto err_exit;
}
}
if (cbus_intr1 & MSC_REQ_DONE) {
pr_debug("sii9234: msc request done\n");
complete(&sii9234->msc_complete);
}
if (cbus_intr1 & MSC_MSG_RECD) {
struct msc_data *data;
pr_debug("sii9234: msc msg received\n");
data = kzalloc(sizeof(struct msc_data), GFP_KERNEL);
if (!data) {
dev_err(&sii9234->pdata->mhl_tx_client->dev,
"failed to allocate msc data");
ret = -ENOMEM;
goto err_exit;
}
data->cmd = MSC_MSG;
cbus_read_reg(sii9234, CBUS_MSC_MSG_CMD_IN, &data->offset);
cbus_read_reg(sii9234, CBUS_MSC_MSG_DATA_IN, &data->data);
list_add_tail(&data->list, &sii9234->msc_data_list);
schedule_work(&sii9234->msc_work);
}
if (cbus_intr2 & WRT_STAT_RECD) {
struct msc_data *data;
bool path_en_changed = false;
pr_debug("sii9234: write status received\n");
sii9234->msc_ready = mhl_status0 & MHL_STATUS_DCAP_READY;
if (!(sii9234->link_mode & MHL_STATUS_PATH_ENABLED) &&
(MHL_STATUS_PATH_ENABLED & mhl_status1)) {
/* PATH_EN{SOURCE} = 0 and PATH_EN{SINK}= 1 */
sii9234->link_mode |= MHL_STATUS_PATH_ENABLED;
path_en_changed = true;
} else if ((sii9234->link_mode & MHL_STATUS_PATH_ENABLED) &&
!(MHL_STATUS_PATH_ENABLED & mhl_status1)) {
/* PATH_EN{SOURCE} = 1 and PATH_EN{SINK}= 0 */
sii9234->link_mode &= ~MHL_STATUS_PATH_ENABLED;
path_en_changed = true;
}
if (path_en_changed) {
data = kzalloc(sizeof(struct msc_data), GFP_KERNEL);
if (!data) {
dev_err(&sii9234->pdata->mhl_tx_client->dev,
"failed to allocate msc data");
ret = -ENOMEM;
goto err_exit;
}
data->cmd = WRITE_STAT;
data->offset = CBUS_MHL_STATUS_OFFSET_1;
data->data = sii9234->link_mode;
list_add_tail(&data->list, &sii9234->msc_data_list);
schedule_work(&sii9234->msc_work);
}
}
if (cbus_intr2 & SET_INT_RECD) {
if (mhl_intr0 & MHL_INT_DCAP_CHG) {
struct msc_data *data;
/*
* devcap[] had already been populated while detection
* callback;now sink(or dongle) is again notiftying some
* capability change.
* TODO: should we read the complete devcap[] again?
*/
pr_debug("sii9234: device capability changed\n");
data = kzalloc(sizeof(struct msc_data), GFP_KERNEL);
if (!data) {
dev_err(&sii9234->pdata->mhl_tx_client->dev,
"failed to allocate msc data");
ret = -ENOMEM;
goto err_exit;
}
data->cmd = READ_DEVCAP;
data->offset = MHL_DEVCAP_DEV_CAT;
list_add_tail(&data->list, &sii9234->msc_data_list);
schedule_work(&sii9234->msc_work);
}
if (mhl_intr0 & MHL_INT_DSCR_CHG) {
/*
* TODO: Peer is done updating the scratchpad
* registers;Source should read the register values from
* local register space
*/
pr_debug("sii9234: scratchpad register change done\n");
}
if (mhl_intr0 & MHL_INT_REQ_WRT) {
struct msc_data *data;
pr_debug("sii9234: request-to-write received\n");
data = kzalloc(sizeof(struct msc_data), GFP_KERNEL);
if (!data) {
dev_err(&sii9234->pdata->mhl_tx_client->dev,
"failed to allocate msc data");
ret = -ENOMEM;
goto err_exit;
}
data->cmd = SET_INT;
data->offset = CBUS_MHL_INTR_OFFSET_0;
/* signal grant-to-write to the peer */
data->data = MHL_INT_GRT_WRT;
list_add_tail(&data->list, &sii9234->msc_data_list);
schedule_work(&sii9234->msc_work);
}
if (mhl_intr0 & MHL_INT_GRT_WRT) {
/* TODO: received a grant-to-write from peer;Source
* should initiate a WRITE_BURST
*/
pr_debug("sii9234: grant-to-write received\n");
}
if (mhl_intr1 & MHL_INT_EDID_CHG)
sii9234_toggle_hpd(sii9234);
}
if (cbus_intr2 & WRT_BURST_RECD)
pr_debug("sii9234: write burst received\n");
err_exit:
cbus_write_reg(sii9234, CBUS_MHL_INTR_REG_0, mhl_intr0);
cbus_write_reg(sii9234, CBUS_MHL_INTR_REG_1, mhl_intr1);
cbus_write_reg(sii9234, CBUS_INT_STATUS_1_REG, cbus_intr1);
cbus_write_reg(sii9234, CBUS_INT_STATUS_2_REG, cbus_intr2);
return ret;
}
static irqreturn_t sii9234_irq_thread(int irq, void *data)
{
struct sii9234_data *sii9234 = data;
int ret;
u8 intr1, intr4, value;
u8 intr1_en, intr4_en;
bool release_otg = false;
mutex_lock(&sii9234->lock);
mhl_tx_read_reg(sii9234, MHL_TX_INTR1_REG, &intr1);
mhl_tx_read_reg(sii9234, MHL_TX_INTR4_REG, &intr4);
mhl_tx_read_reg(sii9234, MHL_TX_INTR1_ENABLE_REG, &intr1_en);
mhl_tx_read_reg(sii9234, MHL_TX_INTR4_ENABLE_REG, &intr4_en);
pr_debug("sii9234: irq %02x/%02x %02x/%02x\n", intr1, intr1_en,
intr4, intr4_en);
if (intr4 & RGND_READY_INT) {
ret = mhl_tx_read_reg(sii9234, MHL_TX_STAT2_REG, &value);
if (ret < 0) {
dev_err(&sii9234->pdata->mhl_tx_client->dev,
"STAT2 reg, err %d\n", ret);
goto err_exit;
}
switch (value & RGND_INTP_MASK) {
case RGND_INTP_OPEN:
pr_debug("RGND Open\n");
sii9234->rgnd = RGND_OPEN;
break;
case RGND_INTP_1K:
pr_debug("RGND 1K\n");
ret = mhl_tx_write_reg(sii9234, MHL_TX_DISC_CTRL1_REG,
0x25);
ret = mhl_send_wake_pulses(sii9234);
sii9234->rgnd = RGND_1K;
break;
case RGND_INTP_2K:
pr_debug("RGND 2K\n");
ret = mhl_send_wake_pulses(sii9234);
sii9234->rgnd = RGND_2K;
break;
case RGND_INTP_SHORT:
pr_debug("RGND Short\n");
sii9234->rgnd = RGND_SHORT;
break;
};
}
if (intr4 & CBUS_LKOUT_INT) {
pr_debug("sii9234: CBUS Lockout Interrupt\n");
sii9234->state = STATE_CBUS_LOCKOUT;
}
if (intr4 & MHL_DISC_FAIL_INT)
sii9234->state = STATE_DISCOVERY_FAILED;
if (intr4 & MHL_EST_INT) {
/* discovery override */
ret = mhl_tx_write_reg(sii9234, MHL_TX_MHLTX_CTL1_REG, 0x10);
/* increase DDC translation layer timer (byte mode) */
cbus_write_reg(sii9234, 0x07, 0x32);
cbus_set_reg(sii9234, 0x44, 1<<1);
/* Keep the discovery enabled. Need RGND interrupt */
ret = mhl_tx_set_reg(sii9234, MHL_TX_DISC_CTRL1_REG, (1<<0));
sii9234->state = STATE_ESTABLISHED;
}
if (intr1 & HPD_CHANGE_INT) {
ret = cbus_read_reg(sii9234, MSC_REQ_ABORT_REASON_REG, &value);
if (value & SET_HPD_DOWNSTREAM) {
/* Downstream HPD Highi */
/* Do we need to send HPD upstream using
* Register 0x79(page0)? Is HPD need to be overriden??
* TODO: See if we need code for overriding HPD OUT
* as per Page 0,0x79 Register
*/
/* Enable TMDS */
sii9234_tmds_control(sii9234, true);
} else {
/*Downstream HPD Low*/
/* Similar to above comments.
* TODO:Do we need to override HPD OUT value
* and do we need to disable TMDS here?
*/
/* Disable TMDS */
sii9234_tmds_control(sii9234, false);
}
}
if (intr1 & RSEN_CHANGE_INT) {
ret = mhl_tx_read_reg(sii9234, MHL_TX_SYSSTAT_REG, &value);
sii9234->rsen = value & RSEN_STATUS;
if (value & RSEN_STATUS) {
pr_info("sii9234: MHL cable connected.. RESN High\n");
} else {
pr_info("sii9234: RSEN lost\n");
/* Once RSEN loss is confirmed,we need to check
* based on cable status and chip power status,whether
* it is SINK Loss(HDMI cable not connected, TV Off)
* or MHL cable disconnection
*/
/* sleep for handling glitch on RSEN */
usleep_range(T_SRC_RXSENSE_DEGLITCH * USEC_PER_MSEC,
T_SRC_RXSENSE_DEGLITCH * USEC_PER_MSEC);
ret = mhl_tx_read_reg(sii9234, MHL_TX_SYSSTAT_REG,
&value);
pr_cont(" sys_stat:%x\n", value);
if ((value & RSEN_STATUS) == 0) {
/* Notify Disconnection to OTG */
if (sii9234->claimed == true) {
disable_irq_nosync(sii9234->irq);
release_otg = true;
}
sii9234_tmds_control(sii9234, false);
sii9234_power_down(sii9234);
}
}
}
if (sii9234->state == STATE_ESTABLISHED) {
ret = sii9234_cbus_irq(sii9234);
if (ret < 0) {
if (sii9234->claimed == true) {
disable_irq_nosync(sii9234->irq);
release_otg = true;
}
sii9234_power_down(sii9234);
}
}
err_exit:
mhl_tx_write_reg(sii9234, MHL_TX_INTR1_REG, intr1);
mhl_tx_write_reg(sii9234, MHL_TX_INTR4_REG, intr4);
mutex_unlock(&sii9234->lock);
pr_debug("si9234: wake_up\n");
wake_up(&sii9234->wq);
if (release_otg) {
sii9234->state = STATE_DISCONNECTING;
otg_id_notify();
}
return IRQ_HANDLED;
}
static int __devinit sii9234_mhl_tx_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
struct sii9234_data *sii9234;
int ret;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -EIO;
sii9234 = kzalloc(sizeof(struct sii9234_data), GFP_KERNEL);
if (!sii9234) {
dev_err(&client->dev, "failed to allocate driver data\n");
return -ENOMEM;
}
sii9234->pdata = client->dev.platform_data;
sii9234->pdata->mhl_tx_client = client;
if (!sii9234->pdata) {
ret = -EINVAL;
goto err_exit;
}
i2c_set_clientdata(client, sii9234);
sii9234->irq = client->irq;
init_waitqueue_head(&sii9234->wq);
mutex_init(&sii9234->lock);
mutex_init(&sii9234->msc_lock);
mutex_init(&sii9234->input_lock);
INIT_WORK(&sii9234->msc_work, sii9234_msc_event);
INIT_LIST_HEAD(&sii9234->msc_data_list);
ret = request_threaded_irq(client->irq, NULL, sii9234_irq_thread,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
"sii9234", sii9234);
if (ret < 0)
goto err_exit;
disable_irq(client->irq);
sii9234->otg_id_nb.detect = sii9234_detection_callback;
sii9234->otg_id_nb.cancel = sii9234_cancel_callback;
sii9234->otg_id_nb.priority = sii9234->pdata->prio;
plist_node_init(&sii9234->otg_id_nb.p, sii9234->pdata->prio);
ret = otg_id_register_notifier(&sii9234->otg_id_nb);
if (ret < 0) {
dev_err(&client->dev, "Unable to register notifier\n");
goto err_exit;
}
sii9234->redetect_wq = create_singlethread_workqueue("sii9234");
if (!sii9234->redetect_wq) {
dev_err(&client->dev, "unable to create workqueue\n");
goto err_exit;
}
INIT_WORK(&sii9234->redetect_work, sii9234_retry_detection);
return 0;
err_exit:
kfree(sii9234);
return ret;
}
static int __devinit sii9234_tpi_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct sii9234_platform_data *pdata = client->dev.platform_data;
pdata->tpi_client = client;
return 0;
}
static int __devinit sii9234_hdmi_rx_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct sii9234_platform_data *pdata = client->dev.platform_data;
pdata->hdmi_rx_client = client;
return 0;
}
static int __devinit sii9234_cbus_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct sii9234_platform_data *pdata = client->dev.platform_data;
pdata->cbus_client = client;
return 0;
}
static int __devexit sii9234_mhl_tx_remove(struct i2c_client *client)
{
return 0;
}
static int __devexit sii9234_tpi_remove(struct i2c_client *client)
{
return 0;
}
static int __devexit sii9234_hdmi_rx_remove(struct i2c_client *client)
{
return 0;
}
static int __devexit sii9234_cbus_remove(struct i2c_client *client)
{
return 0;
}
static const struct i2c_device_id sii9234_mhl_tx_id[] = {
{"sii9234_mhl_tx", 0},
{}
};
static const struct i2c_device_id sii9234_tpi_id[] = {
{"sii9234_tpi", 0},
{}
};
static const struct i2c_device_id sii9234_hdmi_rx_id[] = {
{"sii9234_hdmi_rx", 0},
{}
};
static const struct i2c_device_id sii9234_cbus_id[] = {
{"sii9234_cbus", 0},
{}
};
MODULE_DEVICE_TABLE(i2c, sii9234_mhl_tx_id);
MODULE_DEVICE_TABLE(i2c, sii9234_tpi_id);
MODULE_DEVICE_TABLE(i2c, sii9234_hdmi_rx_id);
MODULE_DEVICE_TABLE(i2c, sii9234_cbus_id);
static struct i2c_driver sii9234_mhl_tx_i2c_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "sii9234_mhl_tx",
},
.id_table = sii9234_mhl_tx_id,
.probe = sii9234_mhl_tx_i2c_probe,
.remove = __devexit_p(sii9234_mhl_tx_remove),
.command = NULL,
};
static struct i2c_driver sii9234_tpi_i2c_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "sii9234_tpi",
},
.id_table = sii9234_tpi_id,
.probe = sii9234_tpi_i2c_probe,
.remove = __devexit_p(sii9234_tpi_remove),
};
static struct i2c_driver sii9234_hdmi_rx_i2c_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "sii9234_hdmi_rx",
},
.id_table = sii9234_hdmi_rx_id,
.probe = sii9234_hdmi_rx_i2c_probe,
.remove = __devexit_p(sii9234_hdmi_rx_remove),
};
static struct i2c_driver sii9234_cbus_i2c_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "sii9234_cbus",
},
.id_table = sii9234_cbus_id,
.probe = sii9234_cbus_i2c_probe,
.remove = __devexit_p(sii9234_cbus_remove),
};
static int __init sii9234_init(void)
{
int ret;
ret = i2c_add_driver(&sii9234_mhl_tx_i2c_driver);
if (ret < 0)
return ret;
ret = i2c_add_driver(&sii9234_tpi_i2c_driver);
if (ret < 0)
goto err_exit1;
ret = i2c_add_driver(&sii9234_hdmi_rx_i2c_driver);
if (ret < 0)
goto err_exit2;
ret = i2c_add_driver(&sii9234_cbus_i2c_driver);
if (ret < 0)
goto err_exit3;
return 0;
err_exit3:
i2c_del_driver(&sii9234_hdmi_rx_i2c_driver);
err_exit2:
i2c_del_driver(&sii9234_tpi_i2c_driver);
err_exit1:
i2c_del_driver(&sii9234_mhl_tx_i2c_driver);
return ret;
}
static void __exit sii9234_exit(void)
{
i2c_del_driver(&sii9234_cbus_i2c_driver);
i2c_del_driver(&sii9234_hdmi_rx_i2c_driver);
i2c_del_driver(&sii9234_tpi_i2c_driver);
i2c_del_driver(&sii9234_mhl_tx_i2c_driver);
}
module_init(sii9234_init);
module_exit(sii9234_exit);