/* * Copyright (C) 2011 Samsung Electronics * * Authors: Adam Hampson * Erik Gilling * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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<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);