mirror of
https://github.com/team-infusion-developers/android_kernel_samsung_msm8976.git
synced 2024-10-31 18:09:19 +00:00
121fb2acfb
Add mutex lock betweeen mhl_sii_config and mhl_sii_device_discovery functions to avoid accesing the variable flag before it is updated. This will avoid accessing the mhl init settings i2c register before turning on the mhl regulator's and gpio's. Change-Id: If3f7fbe3d6ab84f101c407c43ac8b410e6df38fe Signed-off-by: Raghavendra Ambadas <rambad@codeaurora.org>
2104 lines
54 KiB
C
2104 lines
54 KiB
C
/* Copyright (c) 2012-2014, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#include <linux/bitops.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/types.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/input.h>
|
|
#include <linux/usb/msm_hsusb.h>
|
|
#include <linux/mhl_8334.h>
|
|
#include <linux/mdss_io_util.h>
|
|
|
|
#include "mdss_fb.h"
|
|
#include "mdss_hdmi_tx.h"
|
|
#include "mdss_hdmi_edid.h"
|
|
#include "mdss.h"
|
|
#include "mdss_panel.h"
|
|
#include "mhl_msc.h"
|
|
#include "mdss_hdmi_mhl.h"
|
|
|
|
#define MHL_DRIVER_NAME "sii8334"
|
|
#define COMPATIBLE_NAME "qcom,mhl-sii8334"
|
|
#define MAX_CURRENT 700000
|
|
|
|
#define pr_debug_intr(...)
|
|
|
|
#define MSC_START_BIT_MSC_CMD (0x01 << 0)
|
|
#define MSC_START_BIT_VS_CMD (0x01 << 1)
|
|
#define MSC_START_BIT_READ_REG (0x01 << 2)
|
|
#define MSC_START_BIT_WRITE_REG (0x01 << 3)
|
|
#define MSC_START_BIT_WRITE_BURST (0x01 << 4)
|
|
|
|
/* supported RCP key code */
|
|
u16 support_rcp_key_code_tbl[] = {
|
|
KEY_ENTER, /* 0x00 Select */
|
|
KEY_UP, /* 0x01 Up */
|
|
KEY_DOWN, /* 0x02 Down */
|
|
KEY_LEFT, /* 0x03 Left */
|
|
KEY_RIGHT, /* 0x04 Right */
|
|
KEY_UNKNOWN, /* 0x05 Right-up */
|
|
KEY_UNKNOWN, /* 0x06 Right-down */
|
|
KEY_UNKNOWN, /* 0x07 Left-up */
|
|
KEY_UNKNOWN, /* 0x08 Left-down */
|
|
KEY_MENU, /* 0x09 Root Menu */
|
|
KEY_OPTION, /* 0x0A Setup Menu */
|
|
KEY_UNKNOWN, /* 0x0B Contents Menu */
|
|
KEY_UNKNOWN, /* 0x0C Favorite Menu */
|
|
KEY_EXIT, /* 0x0D 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, /* 0x20 NUMERIC_0 */
|
|
KEY_NUMERIC_1, /* 0x21 NUMERIC_1 */
|
|
KEY_NUMERIC_2, /* 0x22 NUMERIC_2 */
|
|
KEY_NUMERIC_3, /* 0x23 NUMERIC_3 */
|
|
KEY_NUMERIC_4, /* 0x24 NUMERIC_4 */
|
|
KEY_NUMERIC_5, /* 0x25 NUMERIC_5 */
|
|
KEY_NUMERIC_6, /* 0x26 NUMERIC_6 */
|
|
KEY_NUMERIC_7, /* 0x27 NUMERIC_7 */
|
|
KEY_NUMERIC_8, /* 0x28 NUMERIC_8 */
|
|
KEY_NUMERIC_9, /* 0x29 NUMERIC_9 */
|
|
KEY_DOT, /* 0x2A Dot */
|
|
KEY_ENTER, /* 0x2B Enter */
|
|
KEY_ESC, /* 0x2C Clear */
|
|
KEY_RESERVED, /* 0x2D */
|
|
KEY_RESERVED, /* 0x2E */
|
|
KEY_RESERVED, /* 0x2F */
|
|
KEY_UNKNOWN, /* 0x30 Channel Up */
|
|
KEY_UNKNOWN, /* 0x31 Channel Down */
|
|
KEY_UNKNOWN, /* 0x32 Previous Channel */
|
|
KEY_UNKNOWN, /* 0x33 Sound Select */
|
|
KEY_UNKNOWN, /* 0x34 Input Select */
|
|
KEY_UNKNOWN, /* 0x35 Show Information */
|
|
KEY_UNKNOWN, /* 0x36 Help */
|
|
KEY_UNKNOWN, /* 0x37 Page Up */
|
|
KEY_UNKNOWN, /* 0x38 Page Down */
|
|
KEY_RESERVED, /* 0x39 */
|
|
KEY_RESERVED,
|
|
KEY_RESERVED,
|
|
KEY_RESERVED,
|
|
KEY_RESERVED,
|
|
KEY_RESERVED,
|
|
KEY_RESERVED, /* 0x3F */
|
|
KEY_RESERVED, /* 0x40 */
|
|
KEY_VOLUMEUP, /* 0x41 Volume Up */
|
|
KEY_VOLUMEDOWN, /* 0x42 Volume Down */
|
|
KEY_MUTE, /* 0x43 Mute */
|
|
KEY_PLAY, /* 0x44 Play */
|
|
KEY_STOP, /* 0x45 Stop */
|
|
KEY_PAUSE, /* 0x46 Pause */
|
|
KEY_UNKNOWN, /* 0x47 Record */
|
|
KEY_REWIND, /* 0x48 Rewind */
|
|
KEY_FASTFORWARD, /* 0x49 Fast Forward */
|
|
KEY_UNKNOWN, /* 0x4A Eject */
|
|
KEY_FORWARD, /* 0x4B Forward */
|
|
KEY_BACK, /* 0x4C Backward */
|
|
KEY_RESERVED, /* 0x4D */
|
|
KEY_RESERVED,
|
|
KEY_RESERVED, /* 0x4F */
|
|
KEY_UNKNOWN, /* 0x50 Angle */
|
|
KEY_UNKNOWN, /* 0x51 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_PLAYPAUSE, /* 0x60 Play Function */
|
|
KEY_PLAYPAUSE, /* 0x61 Pause_Play Function */
|
|
KEY_UNKNOWN, /* 0x62 Record Function */
|
|
KEY_PAUSE, /* 0x63 Pause Record Function */
|
|
KEY_STOP, /* 0x64 Stop Function */
|
|
KEY_MUTE, /* 0x65 Mute Function */
|
|
KEY_UNKNOWN, /* 0x66 Restore Volume Function */
|
|
KEY_UNKNOWN, /* 0x67 Tune Function */
|
|
KEY_UNKNOWN, /* 0x68 Select Media Function */
|
|
KEY_RESERVED, /* 0x69 */
|
|
KEY_RESERVED,
|
|
KEY_RESERVED,
|
|
KEY_RESERVED,
|
|
KEY_RESERVED,
|
|
KEY_RESERVED,
|
|
KEY_RESERVED,
|
|
KEY_RESERVED, /* 0x70 */
|
|
KEY_BLUE, /* 0x71 F1 */
|
|
KEY_RED, /* 0x72 F2 */
|
|
KEY_GREEN, /* 0x73 F3 */
|
|
KEY_YELLOW, /* 0x74 F4 */
|
|
KEY_UNKNOWN, /* 0x75 F5 */
|
|
KEY_RESERVED, /* 0x76 */
|
|
KEY_RESERVED,
|
|
KEY_RESERVED,
|
|
KEY_RESERVED,
|
|
KEY_RESERVED,
|
|
KEY_RESERVED,
|
|
KEY_RESERVED,
|
|
KEY_RESERVED, /* 0x7D */
|
|
KEY_VENDOR, /* Vendor Specific */
|
|
KEY_RESERVED, /* 0x7F */
|
|
};
|
|
|
|
|
|
uint8_t slave_addrs[MAX_PAGES] = {
|
|
DEV_PAGE_TPI_0 ,
|
|
DEV_PAGE_TX_L0_0 ,
|
|
DEV_PAGE_TX_L1_0 ,
|
|
DEV_PAGE_TX_2_0 ,
|
|
DEV_PAGE_TX_3_0 ,
|
|
DEV_PAGE_CBUS ,
|
|
DEV_PAGE_DDC_EDID ,
|
|
DEV_PAGE_DDC_SEGM ,
|
|
};
|
|
|
|
static irqreturn_t mhl_tx_isr(int irq, void *dev_id);
|
|
static void switch_mode(struct mhl_tx_ctrl *mhl_ctrl,
|
|
enum mhl_st_type to_mode, bool hpd_off);
|
|
static void mhl_init_reg_settings(struct mhl_tx_ctrl *mhl_ctrl,
|
|
bool mhl_disc_en);
|
|
static int mhl_gpio_config(struct mhl_tx_ctrl *mhl_ctrl, int on);
|
|
static int mhl_vreg_config(struct mhl_tx_ctrl *mhl_ctrl, uint8_t on);
|
|
|
|
int mhl_i2c_reg_read(struct i2c_client *client,
|
|
uint8_t slave_addr_index, uint8_t reg_offset)
|
|
{
|
|
int rc = -1;
|
|
uint8_t buffer = 0;
|
|
|
|
rc = mdss_i2c_byte_read(client, slave_addrs[slave_addr_index],
|
|
reg_offset, &buffer);
|
|
if (rc) {
|
|
pr_err("%s: slave=%x, off=%x\n",
|
|
__func__, slave_addrs[slave_addr_index], reg_offset);
|
|
return rc;
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
|
|
int mhl_i2c_reg_write(struct i2c_client *client,
|
|
uint8_t slave_addr_index, uint8_t reg_offset,
|
|
uint8_t value)
|
|
{
|
|
return mdss_i2c_byte_write(client, slave_addrs[slave_addr_index],
|
|
reg_offset, &value);
|
|
}
|
|
|
|
void mhl_i2c_reg_modify(struct i2c_client *client,
|
|
uint8_t slave_addr_index, uint8_t reg_offset,
|
|
uint8_t mask, uint8_t val)
|
|
{
|
|
uint8_t temp;
|
|
|
|
temp = mhl_i2c_reg_read(client, slave_addr_index, reg_offset);
|
|
temp &= (~mask);
|
|
temp |= (mask & val);
|
|
mhl_i2c_reg_write(client, slave_addr_index, reg_offset, temp);
|
|
}
|
|
|
|
|
|
static int mhl_tx_get_dt_data(struct device *dev,
|
|
struct mhl_tx_platform_data *pdata)
|
|
{
|
|
int i, rc = 0;
|
|
struct device_node *of_node = NULL;
|
|
struct dss_gpio *temp_gpio = NULL;
|
|
struct platform_device *hdmi_pdev = NULL;
|
|
struct device_node *hdmi_tx_node = NULL;
|
|
int dt_gpio;
|
|
i = 0;
|
|
|
|
if (!dev || !pdata) {
|
|
pr_err("%s: invalid input\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
of_node = dev->of_node;
|
|
if (!of_node) {
|
|
pr_err("%s: invalid of_node\n", __func__);
|
|
goto error;
|
|
}
|
|
|
|
pr_debug("%s: id=%d\n", __func__, dev->id);
|
|
|
|
/* GPIOs */
|
|
temp_gpio = NULL;
|
|
temp_gpio = devm_kzalloc(dev, sizeof(struct dss_gpio), GFP_KERNEL);
|
|
pr_debug("%s: gpios allocd\n", __func__);
|
|
if (!(temp_gpio)) {
|
|
pr_err("%s: can't alloc %d gpio mem\n", __func__, i);
|
|
goto error;
|
|
}
|
|
/* RESET */
|
|
dt_gpio = of_get_named_gpio(of_node, "mhl-rst-gpio", 0);
|
|
if (dt_gpio < 0) {
|
|
pr_err("%s: Can't get mhl-rst-gpio\n", __func__);
|
|
goto error;
|
|
}
|
|
|
|
temp_gpio->gpio = dt_gpio;
|
|
snprintf(temp_gpio->gpio_name, 32, "%s", "mhl-rst-gpio");
|
|
pr_debug("%s: rst gpio=[%d]\n", __func__,
|
|
temp_gpio->gpio);
|
|
pdata->gpios[MHL_TX_RESET_GPIO] = temp_gpio;
|
|
|
|
/* PWR */
|
|
temp_gpio = NULL;
|
|
temp_gpio = devm_kzalloc(dev, sizeof(struct dss_gpio), GFP_KERNEL);
|
|
pr_debug("%s: gpios allocd\n", __func__);
|
|
if (!(temp_gpio)) {
|
|
pr_err("%s: can't alloc %d gpio mem\n", __func__, i);
|
|
goto error;
|
|
}
|
|
dt_gpio = of_get_named_gpio(of_node, "mhl-pwr-gpio", 0);
|
|
if (dt_gpio < 0) {
|
|
pr_err("%s: Can't get mhl-pwr-gpio\n", __func__);
|
|
goto error;
|
|
}
|
|
|
|
temp_gpio->gpio = dt_gpio;
|
|
snprintf(temp_gpio->gpio_name, 32, "%s", "mhl-pwr-gpio");
|
|
pr_debug("%s: pmic gpio=[%d]\n", __func__,
|
|
temp_gpio->gpio);
|
|
pdata->gpios[MHL_TX_PMIC_PWR_GPIO] = temp_gpio;
|
|
|
|
/* INTR */
|
|
temp_gpio = NULL;
|
|
temp_gpio = devm_kzalloc(dev, sizeof(struct dss_gpio), GFP_KERNEL);
|
|
pr_debug("%s: gpios allocd\n", __func__);
|
|
if (!(temp_gpio)) {
|
|
pr_err("%s: can't alloc %d gpio mem\n", __func__, i);
|
|
goto error;
|
|
}
|
|
dt_gpio = of_get_named_gpio(of_node, "mhl-intr-gpio", 0);
|
|
if (dt_gpio < 0) {
|
|
pr_err("%s: Can't get mhl-intr-gpio\n", __func__);
|
|
goto error;
|
|
}
|
|
|
|
temp_gpio->gpio = dt_gpio;
|
|
snprintf(temp_gpio->gpio_name, 32, "%s", "mhl-intr-gpio");
|
|
pr_debug("%s: intr gpio=[%d]\n", __func__,
|
|
temp_gpio->gpio);
|
|
pdata->gpios[MHL_TX_INTR_GPIO] = temp_gpio;
|
|
|
|
/* parse phandle for hdmi tx */
|
|
hdmi_tx_node = of_parse_phandle(of_node, "qcom,hdmi-tx-map", 0);
|
|
if (!hdmi_tx_node) {
|
|
pr_err("%s: can't find hdmi phandle\n", __func__);
|
|
goto error;
|
|
}
|
|
|
|
hdmi_pdev = of_find_device_by_node(hdmi_tx_node);
|
|
if (!hdmi_pdev) {
|
|
pr_err("%s: can't find the device by node\n", __func__);
|
|
goto error;
|
|
}
|
|
pr_debug("%s: hdmi_pdev [0X%x] to pdata->pdev\n",
|
|
__func__, (unsigned int)hdmi_pdev);
|
|
|
|
pdata->hdmi_pdev = hdmi_pdev;
|
|
|
|
return 0;
|
|
error:
|
|
pr_err("%s: ret due to err\n", __func__);
|
|
for (i = 0; i < MHL_TX_MAX_GPIO; i++)
|
|
if (pdata->gpios[i])
|
|
devm_kfree(dev, pdata->gpios[i]);
|
|
return rc;
|
|
} /* mhl_tx_get_dt_data */
|
|
|
|
static int mhl_sii_reset_pin(struct mhl_tx_ctrl *mhl_ctrl, int on)
|
|
{
|
|
if (mhl_ctrl->pdata->gpios[MHL_TX_RESET_GPIO]) {
|
|
gpio_set_value(
|
|
mhl_ctrl->pdata->gpios[MHL_TX_RESET_GPIO]->gpio,
|
|
on);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int mhl_sii_wait_for_rgnd(struct mhl_tx_ctrl *mhl_ctrl)
|
|
{
|
|
int timeout;
|
|
|
|
pr_debug("%s:%u\n", __func__, __LINE__);
|
|
|
|
if (mhl_ctrl->mhl_mode) {
|
|
pr_debug("%s: already in mhl mode\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
INIT_COMPLETION(mhl_ctrl->rgnd_done);
|
|
/*
|
|
* after toggling reset line and enabling disc
|
|
* tx can take a while to generate intr
|
|
*/
|
|
timeout = wait_for_completion_timeout
|
|
(&mhl_ctrl->rgnd_done, HZ * 3);
|
|
if (!timeout) {
|
|
/*
|
|
* most likely nothing plugged in USB
|
|
* USB HOST connected or already in USB mode
|
|
*/
|
|
pr_warn("%s:%u timedout\n", __func__, __LINE__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mhl_sii_config(struct mhl_tx_ctrl *mhl_ctrl, bool on)
|
|
{
|
|
int rc = 0;
|
|
struct i2c_client *client = NULL;
|
|
|
|
if (!mhl_ctrl) {
|
|
pr_err("%s: ctrl is NULL\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
client = mhl_ctrl->i2c_handle;
|
|
|
|
mutex_lock(&mhl_ctrl->sii_config_lock);
|
|
if (on && !mhl_ctrl->irq_req_done) {
|
|
rc = mhl_vreg_config(mhl_ctrl, 1);
|
|
if (rc) {
|
|
pr_err("%s: vreg init failed [%d]\n",
|
|
__func__, rc);
|
|
rc = -ENODEV;
|
|
goto vreg_config_error;
|
|
}
|
|
|
|
rc = mhl_gpio_config(mhl_ctrl, 1);
|
|
if (rc) {
|
|
pr_err("%s: gpio init failed [%d]\n",
|
|
__func__, rc);
|
|
rc = -ENODEV;
|
|
goto vreg_config_error;
|
|
}
|
|
|
|
rc = request_threaded_irq(mhl_ctrl->i2c_handle->irq, NULL,
|
|
&mhl_tx_isr, IRQF_TRIGGER_LOW | IRQF_ONESHOT,
|
|
client->dev.driver->name, mhl_ctrl);
|
|
if (rc) {
|
|
pr_err("%s: request_threaded_irq failed, status: %d\n",
|
|
__func__, rc);
|
|
rc = -ENODEV;
|
|
goto vreg_config_error;
|
|
} else {
|
|
mhl_ctrl->irq_req_done = true;
|
|
/* wait for i2c interrupt line to be activated */
|
|
msleep(100);
|
|
}
|
|
} else if (!on && mhl_ctrl->irq_req_done) {
|
|
free_irq(mhl_ctrl->i2c_handle->irq, mhl_ctrl);
|
|
mhl_gpio_config(mhl_ctrl, 0);
|
|
mhl_vreg_config(mhl_ctrl, 0);
|
|
mhl_ctrl->irq_req_done = false;
|
|
}
|
|
|
|
vreg_config_error:
|
|
mutex_unlock(&mhl_ctrl->sii_config_lock);
|
|
return rc;
|
|
}
|
|
|
|
static void mhl_sii_disc_intr_work(struct work_struct *work)
|
|
{
|
|
struct mhl_tx_ctrl *mhl_ctrl = NULL;
|
|
|
|
mhl_ctrl = container_of(work, struct mhl_tx_ctrl, mhl_intr_work);
|
|
|
|
mhl_sii_config(mhl_ctrl, false);
|
|
}
|
|
|
|
/* USB_HANDSHAKING FUNCTIONS */
|
|
static int mhl_sii_device_discovery(void *data, int id,
|
|
void (*usb_notify_cb)(void *, int), void *ctx)
|
|
{
|
|
int rc;
|
|
struct mhl_tx_ctrl *mhl_ctrl = data;
|
|
unsigned long flags;
|
|
|
|
if (id) {
|
|
/* When MHL cable is disconnected we get a sii8334
|
|
* mhl_disconnect interrupt which is handled separately.
|
|
*/
|
|
pr_debug("%s: USB ID pin high\n", __func__);
|
|
return id;
|
|
}
|
|
|
|
if (!mhl_ctrl || !usb_notify_cb) {
|
|
pr_warn("%s: cb || ctrl is NULL\n", __func__);
|
|
/* return "USB" so caller can proceed */
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!mhl_ctrl->notify_usb_online) {
|
|
mhl_ctrl->notify_usb_online = usb_notify_cb;
|
|
mhl_ctrl->notify_ctx = ctx;
|
|
}
|
|
|
|
flush_work(&mhl_ctrl->mhl_intr_work);
|
|
|
|
rc = mhl_sii_config(mhl_ctrl, true);
|
|
if (rc) {
|
|
pr_err("%s: Failed to config vreg/gpio\n", __func__);
|
|
return rc;
|
|
}
|
|
|
|
if (!mhl_ctrl->disc_enabled) {
|
|
spin_lock_irqsave(&mhl_ctrl->lock, flags);
|
|
mhl_ctrl->tx_powered_off = false;
|
|
spin_unlock_irqrestore(&mhl_ctrl->lock, flags);
|
|
mhl_sii_reset_pin(mhl_ctrl, 0);
|
|
msleep(50);
|
|
mhl_sii_reset_pin(mhl_ctrl, 1);
|
|
/* chipset PR recommends waiting for at least 100 ms
|
|
* the chipset needs longer to come out of D3 state.
|
|
*/
|
|
msleep(100);
|
|
mhl_init_reg_settings(mhl_ctrl, true);
|
|
/* allow tx to enable dev disc after D3 state */
|
|
msleep(100);
|
|
if (mhl_sii_wait_for_rgnd(mhl_ctrl)) {
|
|
pr_err("%s: discovery timeout\n", __func__);
|
|
|
|
mhl_sii_config(mhl_ctrl, false);
|
|
|
|
return -EAGAIN;
|
|
}
|
|
} else {
|
|
if (mhl_ctrl->cur_state == POWER_STATE_D3) {
|
|
mhl_sii_wait_for_rgnd(mhl_ctrl);
|
|
} else {
|
|
/* in MHL mode */
|
|
pr_debug("%s:%u\n", __func__, __LINE__);
|
|
}
|
|
}
|
|
|
|
rc = mhl_ctrl->mhl_mode ? 0 : 1;
|
|
|
|
pr_debug("%s: ret result: %s\n", __func__, rc ? "usb" : " mhl");
|
|
return rc;
|
|
}
|
|
|
|
static int mhl_power_get_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct mhl_tx_ctrl *mhl_ctrl =
|
|
container_of(psy, struct mhl_tx_ctrl, mhl_psy);
|
|
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
|
val->intval = mhl_ctrl->current_val;
|
|
break;
|
|
case POWER_SUPPLY_PROP_PRESENT:
|
|
val->intval = mhl_ctrl->vbus_active;
|
|
break;
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
val->intval = mhl_ctrl->vbus_active && mhl_ctrl->mhl_mode;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int mhl_power_set_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
const union power_supply_propval *val)
|
|
{
|
|
struct mhl_tx_ctrl *mhl_ctrl =
|
|
container_of(psy, struct mhl_tx_ctrl, mhl_psy);
|
|
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_PRESENT:
|
|
mhl_ctrl->vbus_active = val->intval;
|
|
if (mhl_ctrl->vbus_active)
|
|
mhl_ctrl->current_val = MAX_CURRENT;
|
|
else
|
|
mhl_ctrl->current_val = 0;
|
|
power_supply_changed(psy);
|
|
break;
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static char *mhl_pm_power_supplied_to[] = {
|
|
"usb",
|
|
};
|
|
|
|
static enum power_supply_property mhl_pm_power_props[] = {
|
|
POWER_SUPPLY_PROP_PRESENT,
|
|
POWER_SUPPLY_PROP_ONLINE,
|
|
POWER_SUPPLY_PROP_CURRENT_MAX,
|
|
};
|
|
|
|
static void cbus_reset(struct mhl_tx_ctrl *mhl_ctrl)
|
|
{
|
|
uint8_t i;
|
|
struct i2c_client *client = mhl_ctrl->i2c_handle;
|
|
|
|
/* Read the chip rev ID */
|
|
mhl_ctrl->chip_rev_id = MHL_SII_PAGE0_RD(0x04);
|
|
pr_debug("MHL: chip rev ID read=[%x]\n", mhl_ctrl->chip_rev_id);
|
|
|
|
/*
|
|
* REG_SRST
|
|
*/
|
|
MHL_SII_REG_NAME_MOD(REG_SRST, BIT3, BIT3);
|
|
msleep(20);
|
|
MHL_SII_REG_NAME_MOD(REG_SRST, BIT3, 0x00);
|
|
/*
|
|
* REG_INTR1 and REG_INTR4
|
|
*/
|
|
MHL_SII_REG_NAME_WR(REG_INTR1_MASK, BIT6);
|
|
MHL_SII_REG_NAME_WR(REG_INTR4_MASK,
|
|
BIT0 | BIT2 | BIT3 | BIT4 | BIT5 | BIT6);
|
|
|
|
if (mhl_ctrl->chip_rev_id < 1)
|
|
MHL_SII_REG_NAME_WR(REG_INTR5_MASK, BIT3 | BIT4);
|
|
else
|
|
MHL_SII_REG_NAME_WR(REG_INTR5_MASK, 0x00);
|
|
|
|
/* Unmask CBUS1 Intrs */
|
|
MHL_SII_REG_NAME_WR(REG_CBUS_INTR_ENABLE,
|
|
BIT2 | BIT3 | BIT4 | BIT5 | BIT6);
|
|
|
|
/* Unmask CBUS2 Intrs */
|
|
MHL_SII_REG_NAME_WR(REG_CBUS_MSC_INT2_ENABLE, BIT2 | BIT3);
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
/*
|
|
* Enable WRITE_STAT interrupt for writes to
|
|
* all 4 MSC Status registers.
|
|
*/
|
|
MHL_SII_CBUS_WR((0xE0 + i), 0xFF);
|
|
|
|
/*
|
|
* Enable SET_INT interrupt for writes to
|
|
* all 4 MSC Interrupt registers.
|
|
*/
|
|
MHL_SII_CBUS_WR((0xF0 + i), 0xFF);
|
|
}
|
|
}
|
|
|
|
static void init_cbus_regs(struct i2c_client *client)
|
|
{
|
|
uint8_t regval;
|
|
|
|
/* Increase DDC translation layer timer*/
|
|
MHL_SII_CBUS_WR(0x0007, 0xF2);
|
|
/* Drive High Time */
|
|
MHL_SII_CBUS_WR(0x0036, 0x0B);
|
|
/* Use programmed timing */
|
|
MHL_SII_CBUS_WR(0x0039, 0x30);
|
|
/* CBUS Drive Strength */
|
|
MHL_SII_CBUS_WR(0x0040, 0x03);
|
|
/*
|
|
* Write initial default settings
|
|
* to devcap regs: default settings
|
|
*/
|
|
MHL_SII_CBUS_WR(0x0080 |
|
|
DEVCAP_OFFSET_DEV_STATE, DEVCAP_VAL_DEV_STATE);
|
|
MHL_SII_CBUS_WR(0x0080 |
|
|
DEVCAP_OFFSET_MHL_VERSION, DEVCAP_VAL_MHL_VERSION);
|
|
MHL_SII_CBUS_WR(0x0080 |
|
|
DEVCAP_OFFSET_DEV_CAT, DEVCAP_VAL_DEV_CAT);
|
|
MHL_SII_CBUS_WR(0x0080 |
|
|
DEVCAP_OFFSET_ADOPTER_ID_H, DEVCAP_VAL_ADOPTER_ID_H);
|
|
MHL_SII_CBUS_WR(0x0080 |
|
|
DEVCAP_OFFSET_ADOPTER_ID_L, DEVCAP_VAL_ADOPTER_ID_L);
|
|
MHL_SII_CBUS_WR(0x0080 | DEVCAP_OFFSET_VID_LINK_MODE,
|
|
DEVCAP_VAL_VID_LINK_MODE);
|
|
MHL_SII_CBUS_WR(0x0080 |
|
|
DEVCAP_OFFSET_AUD_LINK_MODE,
|
|
DEVCAP_VAL_AUD_LINK_MODE);
|
|
MHL_SII_CBUS_WR(0x0080 |
|
|
DEVCAP_OFFSET_VIDEO_TYPE, DEVCAP_VAL_VIDEO_TYPE);
|
|
MHL_SII_CBUS_WR(0x0080 |
|
|
DEVCAP_OFFSET_LOG_DEV_MAP, DEVCAP_VAL_LOG_DEV_MAP);
|
|
MHL_SII_CBUS_WR(0x0080 |
|
|
DEVCAP_OFFSET_BANDWIDTH, DEVCAP_VAL_BANDWIDTH);
|
|
MHL_SII_CBUS_WR(0x0080 |
|
|
DEVCAP_OFFSET_FEATURE_FLAG, DEVCAP_VAL_FEATURE_FLAG);
|
|
MHL_SII_CBUS_WR(0x0080 |
|
|
DEVCAP_OFFSET_DEVICE_ID_H, DEVCAP_VAL_DEVICE_ID_H);
|
|
MHL_SII_CBUS_WR(0x0080 |
|
|
DEVCAP_OFFSET_DEVICE_ID_L, DEVCAP_VAL_DEVICE_ID_L);
|
|
MHL_SII_CBUS_WR(0x0080 |
|
|
DEVCAP_OFFSET_SCRATCHPAD_SIZE,
|
|
DEVCAP_VAL_SCRATCHPAD_SIZE);
|
|
MHL_SII_CBUS_WR(0x0080 |
|
|
DEVCAP_OFFSET_INT_STAT_SIZE,
|
|
DEVCAP_VAL_INT_STAT_SIZE);
|
|
MHL_SII_CBUS_WR(0x0080 |
|
|
DEVCAP_OFFSET_RESERVED, DEVCAP_VAL_RESERVED);
|
|
|
|
/* Make bits 2,3 (initiator timeout) to 1,1
|
|
* for register CBUS_LINK_CONTROL_2
|
|
* REG_CBUS_LINK_CONTROL_2
|
|
*/
|
|
regval = MHL_SII_CBUS_RD(0x0031);
|
|
regval = (regval | 0x0C);
|
|
/* REG_CBUS_LINK_CONTROL_2 */
|
|
MHL_SII_CBUS_WR(0x0031, regval);
|
|
/* REG_MSC_TIMEOUT_LIMIT */
|
|
MHL_SII_CBUS_WR(0x0022, 0x0F);
|
|
/* REG_CBUS_LINK_CONTROL_1 */
|
|
MHL_SII_CBUS_WR(0x0030, 0x01);
|
|
/* disallow vendor specific commands */
|
|
MHL_SII_CBUS_MOD(0x002E, BIT4, BIT4);
|
|
}
|
|
|
|
/*
|
|
* Configure the initial reg settings
|
|
*/
|
|
static void mhl_init_reg_settings(struct mhl_tx_ctrl *mhl_ctrl,
|
|
bool mhl_disc_en)
|
|
{
|
|
uint8_t regval;
|
|
|
|
/*
|
|
* ============================================
|
|
* POWER UP
|
|
* ============================================
|
|
*/
|
|
struct i2c_client *client = mhl_ctrl->i2c_handle;
|
|
|
|
/* Power up 1.2V core */
|
|
MHL_SII_PAGE1_WR(0x003D, 0x3F);
|
|
/* Enable Tx PLL Clock */
|
|
MHL_SII_PAGE2_WR(0x0011, 0x01);
|
|
/* Enable Tx Clock Path and Equalizer */
|
|
MHL_SII_PAGE2_WR(0x0012, 0x11);
|
|
/* Tx Source Termination ON */
|
|
MHL_SII_REG_NAME_WR(REG_MHLTX_CTL1, 0x10);
|
|
/* Enable 1X MHL Clock output */
|
|
MHL_SII_REG_NAME_WR(REG_MHLTX_CTL6, 0xBC);
|
|
/* Tx Differential Driver Config */
|
|
MHL_SII_REG_NAME_WR(REG_MHLTX_CTL2, 0x3C);
|
|
MHL_SII_REG_NAME_WR(REG_MHLTX_CTL4, 0xC8);
|
|
/* PLL Bandwidth Control */
|
|
MHL_SII_REG_NAME_WR(REG_MHLTX_CTL7, 0x03);
|
|
MHL_SII_REG_NAME_WR(REG_MHLTX_CTL8, 0x0A);
|
|
/*
|
|
* ============================================
|
|
* Analog PLL Control
|
|
* ============================================
|
|
*/
|
|
/* Enable Rx PLL clock */
|
|
MHL_SII_REG_NAME_WR(REG_TMDS_CCTRL, 0x08);
|
|
MHL_SII_PAGE0_WR(0x00F8, 0x8C);
|
|
MHL_SII_PAGE0_WR(0x0085, 0x02);
|
|
MHL_SII_PAGE2_WR(0x0000, 0x00);
|
|
regval = MHL_SII_PAGE2_RD(0x0005);
|
|
regval &= ~BIT5;
|
|
MHL_SII_PAGE2_WR(0x0005, regval);
|
|
MHL_SII_PAGE2_WR(0x0013, 0x60);
|
|
/* PLL Cal ref sel */
|
|
MHL_SII_PAGE2_WR(0x0017, 0x03);
|
|
/* VCO Cal */
|
|
MHL_SII_PAGE2_WR(0x001A, 0x20);
|
|
/* Auto EQ */
|
|
MHL_SII_PAGE2_WR(0x0022, 0xE0);
|
|
MHL_SII_PAGE2_WR(0x0023, 0xC0);
|
|
MHL_SII_PAGE2_WR(0x0024, 0xA0);
|
|
MHL_SII_PAGE2_WR(0x0025, 0x80);
|
|
MHL_SII_PAGE2_WR(0x0026, 0x60);
|
|
MHL_SII_PAGE2_WR(0x0027, 0x40);
|
|
MHL_SII_PAGE2_WR(0x0028, 0x20);
|
|
MHL_SII_PAGE2_WR(0x0029, 0x00);
|
|
/* Rx PLL Bandwidth 4MHz */
|
|
MHL_SII_PAGE2_WR(0x0031, 0x0A);
|
|
/* Rx PLL Bandwidth value from I2C */
|
|
MHL_SII_PAGE2_WR(0x0045, 0x06);
|
|
MHL_SII_PAGE2_WR(0x004B, 0x06);
|
|
MHL_SII_PAGE2_WR(0x004C, 0x60);
|
|
/* Manual zone control */
|
|
MHL_SII_PAGE2_WR(0x004C, 0xE0);
|
|
/* PLL Mode value */
|
|
MHL_SII_PAGE2_WR(0x004D, 0x00);
|
|
MHL_SII_PAGE0_WR(0x0008, 0x35);
|
|
/*
|
|
* Discovery Control and Status regs
|
|
* Setting De-glitch time to 50 ms (default)
|
|
* Switch Control Disabled
|
|
*/
|
|
MHL_SII_REG_NAME_WR(REG_DISC_CTRL2, 0xAD);
|
|
/* 1.8V CBUS VTH */
|
|
MHL_SII_REG_NAME_WR(REG_DISC_CTRL5, 0x57);
|
|
/* RGND and single Discovery attempt */
|
|
MHL_SII_REG_NAME_WR(REG_DISC_CTRL6, 0x11);
|
|
/* Ignore VBUS */
|
|
MHL_SII_REG_NAME_WR(REG_DISC_CTRL8, 0x82);
|
|
|
|
/* Enable CBUS Discovery */
|
|
if (mhl_disc_en) {
|
|
MHL_SII_REG_NAME_WR(REG_DISC_CTRL9, 0x24);
|
|
/* Enable MHL Discovery */
|
|
MHL_SII_REG_NAME_WR(REG_DISC_CTRL1, 0x27);
|
|
/* Pull-up resistance off for IDLE state */
|
|
MHL_SII_REG_NAME_WR(REG_DISC_CTRL4, 0x8C);
|
|
} else {
|
|
MHL_SII_REG_NAME_WR(REG_DISC_CTRL9, 0x26);
|
|
/* Disable MHL Discovery */
|
|
MHL_SII_REG_NAME_WR(REG_DISC_CTRL1, 0x26);
|
|
MHL_SII_REG_NAME_WR(REG_DISC_CTRL4, 0x8C);
|
|
}
|
|
|
|
MHL_SII_REG_NAME_WR(REG_DISC_CTRL7, 0x20);
|
|
/* MHL CBUS Discovery - immediate comm. */
|
|
MHL_SII_REG_NAME_WR(REG_DISC_CTRL3, 0x86);
|
|
|
|
MHL_SII_PAGE3_WR(0x3C, 0x80);
|
|
|
|
MHL_SII_REG_NAME_MOD(REG_INT_CTRL,
|
|
(BIT6 | BIT5 | BIT4), (BIT6 | BIT4));
|
|
|
|
/* Enable Auto Soft RESET */
|
|
MHL_SII_REG_NAME_WR(REG_SRST, 0x084);
|
|
/* HDMI Transcode mode enable */
|
|
MHL_SII_PAGE0_WR(0x000D, 0x1C);
|
|
|
|
cbus_reset(mhl_ctrl);
|
|
init_cbus_regs(client);
|
|
}
|
|
|
|
|
|
static void switch_mode(struct mhl_tx_ctrl *mhl_ctrl, enum mhl_st_type to_mode,
|
|
bool hpd_off)
|
|
{
|
|
struct i2c_client *client = mhl_ctrl->i2c_handle;
|
|
unsigned long flags;
|
|
int rc;
|
|
struct msm_hdmi_mhl_ops *hdmi_mhl_ops = mhl_ctrl->hdmi_mhl_ops;
|
|
|
|
pr_debug("%s: tx pwr on\n", __func__);
|
|
spin_lock_irqsave(&mhl_ctrl->lock, flags);
|
|
mhl_ctrl->tx_powered_off = false;
|
|
spin_unlock_irqrestore(&mhl_ctrl->lock, flags);
|
|
|
|
switch (to_mode) {
|
|
case POWER_STATE_D0_NO_MHL:
|
|
mhl_ctrl->cur_state = to_mode;
|
|
mhl_init_reg_settings(mhl_ctrl, true);
|
|
/* REG_DISC_CTRL1 */
|
|
MHL_SII_REG_NAME_MOD(REG_DISC_CTRL1, BIT1 | BIT0, BIT0);
|
|
|
|
/* TPI_DEVICE_POWER_STATE_CTRL_REG */
|
|
mhl_i2c_reg_modify(client, TX_PAGE_TPI, 0x001E, BIT1 | BIT0,
|
|
0x00);
|
|
break;
|
|
case POWER_STATE_D0_MHL:
|
|
mhl_ctrl->cur_state = to_mode;
|
|
break;
|
|
case POWER_STATE_D3:
|
|
if (mhl_ctrl->cur_state == POWER_STATE_D3) {
|
|
pr_debug("%s: mhl tx already in low power mode\n",
|
|
__func__);
|
|
break;
|
|
}
|
|
|
|
/* Force HPD to 0 when not in MHL mode. */
|
|
mhl_drive_hpd(mhl_ctrl, HPD_DOWN);
|
|
mhl_tmds_ctrl(mhl_ctrl, TMDS_DISABLE);
|
|
/*
|
|
* Change TMDS termination to high impedance
|
|
* on disconnection.
|
|
*/
|
|
MHL_SII_REG_NAME_WR(REG_MHLTX_CTL1, 0xD0);
|
|
msleep(50);
|
|
if (!mhl_ctrl->disc_enabled)
|
|
MHL_SII_REG_NAME_MOD(REG_DISC_CTRL1, BIT1 | BIT0, 0x00);
|
|
if (hdmi_mhl_ops && hpd_off) {
|
|
rc = hdmi_mhl_ops->set_upstream_hpd(
|
|
mhl_ctrl->pdata->hdmi_pdev, 0);
|
|
pr_debug("%s: hdmi unset hpd %s\n", __func__,
|
|
rc ? "failed" : "passed");
|
|
}
|
|
mhl_ctrl->cur_state = POWER_STATE_D3;
|
|
mhl_ctrl->mhl_mode = 0;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static bool is_mhl_powered(void *mhl_ctx)
|
|
{
|
|
struct mhl_tx_ctrl *mhl_ctrl = (struct mhl_tx_ctrl *)mhl_ctx;
|
|
unsigned long flags;
|
|
bool r = false;
|
|
|
|
spin_lock_irqsave(&mhl_ctrl->lock, flags);
|
|
if (mhl_ctrl->tx_powered_off)
|
|
r = false;
|
|
else
|
|
r = true;
|
|
spin_unlock_irqrestore(&mhl_ctrl->lock, flags);
|
|
|
|
pr_debug("%s: ret pwr state as %x\n", __func__, r);
|
|
return r;
|
|
}
|
|
|
|
void mhl_tmds_ctrl(struct mhl_tx_ctrl *mhl_ctrl, uint8_t on)
|
|
{
|
|
struct i2c_client *client = mhl_ctrl->i2c_handle;
|
|
|
|
if (on) {
|
|
MHL_SII_REG_NAME_MOD(REG_TMDS_CCTRL, BIT4, BIT4);
|
|
mhl_ctrl->tmds_en_state = true;
|
|
} else {
|
|
MHL_SII_REG_NAME_MOD(REG_TMDS_CCTRL, BIT4, 0x00);
|
|
mhl_ctrl->tmds_en_state = false;
|
|
}
|
|
}
|
|
|
|
void mhl_drive_hpd(struct mhl_tx_ctrl *mhl_ctrl, uint8_t to_state)
|
|
{
|
|
struct i2c_client *client = mhl_ctrl->i2c_handle;
|
|
unsigned long flags;
|
|
|
|
pr_debug("%s: To state=[0x%x]\n", __func__, to_state);
|
|
if (to_state == HPD_UP) {
|
|
/*
|
|
* Drive HPD to UP state
|
|
* Set HPD_OUT_OVR_EN = HPD State
|
|
* EDID read and Un-force HPD (from low)
|
|
* propogate to src let HPD float by clearing
|
|
* HPD OUT OVRRD EN
|
|
*/
|
|
spin_lock_irqsave(&mhl_ctrl->lock, flags);
|
|
mhl_ctrl->tx_powered_off = false;
|
|
spin_unlock_irqrestore(&mhl_ctrl->lock, flags);
|
|
MHL_SII_REG_NAME_MOD(REG_INT_CTRL, BIT4, 0);
|
|
} else {
|
|
/* Drive HPD to DOWN state */
|
|
MHL_SII_REG_NAME_MOD(REG_INT_CTRL, (BIT4 | BIT5), BIT4);
|
|
}
|
|
}
|
|
|
|
static void mhl_msm_connection(struct mhl_tx_ctrl *mhl_ctrl)
|
|
{
|
|
uint8_t val;
|
|
struct i2c_client *client = mhl_ctrl->i2c_handle;
|
|
|
|
pr_debug("%s: cur st [0x%x]\n", __func__,
|
|
mhl_ctrl->cur_state);
|
|
|
|
if (mhl_ctrl->cur_state == POWER_STATE_D0_MHL) {
|
|
/* Already in D0 - MHL power state */
|
|
pr_err("%s: cur st not D0\n", __func__);
|
|
return;
|
|
}
|
|
switch_mode(mhl_ctrl, POWER_STATE_D0_MHL, true);
|
|
|
|
MHL_SII_REG_NAME_WR(REG_MHLTX_CTL1, 0x10);
|
|
MHL_SII_CBUS_WR(0x07, 0xF2);
|
|
|
|
/*
|
|
* Keep the discovery enabled. Need RGND interrupt
|
|
* Possibly chip disables discovery after MHL_EST??
|
|
* Need to re-enable here
|
|
*/
|
|
val = MHL_SII_PAGE3_RD(0x10);
|
|
MHL_SII_PAGE3_WR(0x10, val | BIT0);
|
|
|
|
/*
|
|
* indicate DCAP_RDY and DCAP_CHG
|
|
* to the peer only after
|
|
* msm conn has been established
|
|
*/
|
|
mhl_msc_send_write_stat(mhl_ctrl,
|
|
MHL_STATUS_REG_CONNECTED_RDY,
|
|
MHL_STATUS_DCAP_RDY);
|
|
|
|
mhl_msc_send_set_int(mhl_ctrl,
|
|
MHL_RCHANGE_INT,
|
|
MHL_INT_DCAP_CHG,
|
|
MSC_PRIORITY_SEND);
|
|
|
|
}
|
|
|
|
static void mhl_msm_disconnection(struct mhl_tx_ctrl *mhl_ctrl)
|
|
{
|
|
struct i2c_client *client = mhl_ctrl->i2c_handle;
|
|
|
|
/* disabling Tx termination */
|
|
MHL_SII_REG_NAME_WR(REG_MHLTX_CTL1, 0xD0);
|
|
switch_mode(mhl_ctrl, POWER_STATE_D3, true);
|
|
mhl_msc_clear(mhl_ctrl);
|
|
}
|
|
|
|
static int mhl_msm_read_rgnd_int(struct mhl_tx_ctrl *mhl_ctrl)
|
|
{
|
|
uint8_t rgnd_imp;
|
|
struct i2c_client *client = mhl_ctrl->i2c_handle;
|
|
struct msm_hdmi_mhl_ops *hdmi_mhl_ops = mhl_ctrl->hdmi_mhl_ops;
|
|
unsigned long flags;
|
|
int rc;
|
|
|
|
spin_lock_irqsave(&mhl_ctrl->lock, flags);
|
|
mhl_ctrl->tx_powered_off = false;
|
|
spin_unlock_irqrestore(&mhl_ctrl->lock, flags);
|
|
|
|
/* DISC STATUS REG 2 */
|
|
rgnd_imp = (mhl_i2c_reg_read(client, TX_PAGE_3, 0x001C) &
|
|
(BIT1 | BIT0));
|
|
pr_debug("imp range read=%02X\n", (int)rgnd_imp);
|
|
|
|
if (0x02 == rgnd_imp) {
|
|
pr_debug("%s: mhl sink\n", __func__);
|
|
if (hdmi_mhl_ops) {
|
|
rc = hdmi_mhl_ops->set_upstream_hpd(
|
|
mhl_ctrl->pdata->hdmi_pdev, 1);
|
|
pr_debug("%s: hdmi set hpd %s\n", __func__,
|
|
rc ? "failed" : "passed");
|
|
}
|
|
mhl_ctrl->mhl_mode = 1;
|
|
power_supply_changed(&mhl_ctrl->mhl_psy);
|
|
if (mhl_ctrl->notify_usb_online)
|
|
mhl_ctrl->notify_usb_online(mhl_ctrl->notify_ctx, 1);
|
|
} else {
|
|
pr_debug("%s: non-mhl sink\n", __func__);
|
|
mhl_ctrl->mhl_mode = 0;
|
|
switch_mode(mhl_ctrl, POWER_STATE_D3, true);
|
|
}
|
|
complete(&mhl_ctrl->rgnd_done);
|
|
return mhl_ctrl->mhl_mode ?
|
|
MHL_DISCOVERY_RESULT_MHL : MHL_DISCOVERY_RESULT_USB;
|
|
}
|
|
|
|
static void force_usb_switch_open(struct mhl_tx_ctrl *mhl_ctrl)
|
|
{
|
|
struct i2c_client *client = mhl_ctrl->i2c_handle;
|
|
|
|
/*disable discovery*/
|
|
MHL_SII_REG_NAME_MOD(REG_DISC_CTRL1, BIT0, 0);
|
|
/* force USB ID switch to open*/
|
|
MHL_SII_REG_NAME_MOD(REG_DISC_CTRL6, BIT6, BIT6);
|
|
MHL_SII_REG_NAME_WR(REG_DISC_CTRL3, 0x86);
|
|
/* force HPD to 0 when not in mhl mode. */
|
|
MHL_SII_REG_NAME_MOD(REG_INT_CTRL, BIT5 | BIT4, BIT4);
|
|
}
|
|
|
|
static void release_usb_switch_open(struct mhl_tx_ctrl *mhl_ctrl)
|
|
{
|
|
struct i2c_client *client = mhl_ctrl->i2c_handle;
|
|
|
|
msleep(50);
|
|
MHL_SII_REG_NAME_MOD(REG_DISC_CTRL6, BIT6, 0x00);
|
|
MHL_SII_REG_NAME_MOD(REG_DISC_CTRL1, BIT0, BIT0);
|
|
}
|
|
|
|
static void scdt_st_chg(struct i2c_client *client)
|
|
{
|
|
uint8_t tmds_cstat;
|
|
uint8_t mhl_fifo_status;
|
|
|
|
/* tmds cstat */
|
|
tmds_cstat = MHL_SII_PAGE3_RD(0x0040);
|
|
pr_debug("%s: tmds cstat: 0x%02x\n", __func__,
|
|
tmds_cstat);
|
|
|
|
if (!(tmds_cstat & BIT1))
|
|
return;
|
|
|
|
mhl_fifo_status = MHL_SII_REG_NAME_RD(REG_INTR5);
|
|
pr_debug("%s: mhl fifo st: 0x%02x\n", __func__,
|
|
mhl_fifo_status);
|
|
if (mhl_fifo_status & 0x0C) {
|
|
MHL_SII_REG_NAME_WR(REG_INTR5, 0x0C);
|
|
pr_debug("%s: mhl fifo rst\n", __func__);
|
|
MHL_SII_REG_NAME_WR(REG_SRST, 0x94);
|
|
MHL_SII_REG_NAME_WR(REG_SRST, 0x84);
|
|
}
|
|
}
|
|
|
|
|
|
static int dev_detect_isr(struct mhl_tx_ctrl *mhl_ctrl)
|
|
{
|
|
uint8_t status, reg;
|
|
struct i2c_client *client = mhl_ctrl->i2c_handle;
|
|
|
|
/* INTR_STATUS4 */
|
|
status = MHL_SII_REG_NAME_RD(REG_INTR4);
|
|
pr_debug("%s: reg int4 st=%02X\n", __func__, status);
|
|
|
|
if ((0x00 == status) &&\
|
|
(mhl_ctrl->cur_state == POWER_STATE_D3)) {
|
|
pr_warn("%s: invalid intr\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
if (0xFF == status) {
|
|
pr_warn("%s: invalid intr 0xff\n", __func__);
|
|
MHL_SII_REG_NAME_WR(REG_INTR4, status);
|
|
return 0;
|
|
}
|
|
|
|
if ((status & BIT0) && (mhl_ctrl->chip_rev_id < 1)) {
|
|
pr_debug("%s: scdt intr\n", __func__);
|
|
scdt_st_chg(client);
|
|
}
|
|
|
|
if (status & BIT1)
|
|
pr_debug("mhl: int4 bit1 set\n");
|
|
|
|
/* mhl_est interrupt */
|
|
if (status & BIT2) {
|
|
pr_debug("%s: mhl_est st=%02X\n", __func__,
|
|
(int) status);
|
|
mhl_msm_connection(mhl_ctrl);
|
|
} else if (status & BIT3) {
|
|
pr_debug("%s: uUSB-a type dev detct\n", __func__);
|
|
power_supply_changed(&mhl_ctrl->mhl_psy);
|
|
mhl_drive_hpd(mhl_ctrl, HPD_DOWN);
|
|
return 0;
|
|
}
|
|
|
|
if (status & BIT5) {
|
|
/* clr intr - reg int4 */
|
|
pr_debug("%s: mhl discon: int4 st=%02X\n", __func__,
|
|
(int)status);
|
|
mhl_ctrl->mhl_det_discon = true;
|
|
|
|
reg = MHL_SII_REG_NAME_RD(REG_INTR4);
|
|
MHL_SII_REG_NAME_WR(REG_INTR4, reg);
|
|
mhl_msm_disconnection(mhl_ctrl);
|
|
power_supply_changed(&mhl_ctrl->mhl_psy);
|
|
if (mhl_ctrl->notify_usb_online)
|
|
mhl_ctrl->notify_usb_online(mhl_ctrl->notify_ctx, 0);
|
|
|
|
queue_work(mhl_ctrl->mhl_workq, &mhl_ctrl->mhl_intr_work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
if ((mhl_ctrl->cur_state != POWER_STATE_D0_NO_MHL) &&\
|
|
(status & BIT6)) {
|
|
/* rgnd rdy Intr */
|
|
pr_debug("%s: rgnd ready intr\n", __func__);
|
|
switch_mode(mhl_ctrl, POWER_STATE_D0_NO_MHL, true);
|
|
mhl_msm_read_rgnd_int(mhl_ctrl);
|
|
}
|
|
|
|
/* Can't succeed at these in D3 */
|
|
if ((mhl_ctrl->cur_state != POWER_STATE_D3) &&\
|
|
(status & BIT4)) {
|
|
/* cbus lockout interrupt?
|
|
* Hardware detection mechanism figures that
|
|
* CBUS line is latched and raises this intr
|
|
* where we force usb switch open and release
|
|
*/
|
|
pr_warn("%s: cbus locked out!\n", __func__);
|
|
force_usb_switch_open(mhl_ctrl);
|
|
release_usb_switch_open(mhl_ctrl);
|
|
}
|
|
MHL_SII_REG_NAME_WR(REG_INTR4, status);
|
|
return 0;
|
|
}
|
|
|
|
static void mhl_misc_isr(struct mhl_tx_ctrl *mhl_ctrl)
|
|
{
|
|
uint8_t intr_5_stat;
|
|
struct i2c_client *client = mhl_ctrl->i2c_handle;
|
|
|
|
/*
|
|
* Clear INT 5
|
|
* INTR5 is related to FIFO underflow/overflow reset
|
|
* which is handled in 8334 by auto FIFO reset
|
|
*/
|
|
intr_5_stat = MHL_SII_REG_NAME_RD(REG_INTR5);
|
|
MHL_SII_REG_NAME_WR(REG_INTR5, intr_5_stat);
|
|
}
|
|
|
|
static void mhl_tx_down(struct mhl_tx_ctrl *mhl_ctrl)
|
|
{
|
|
struct i2c_client *client = mhl_ctrl->i2c_handle;
|
|
unsigned long flags;
|
|
uint8_t reg;
|
|
|
|
switch_mode(mhl_ctrl, POWER_STATE_D3, true);
|
|
|
|
reg = MHL_SII_REG_NAME_RD(REG_INTR1);
|
|
MHL_SII_REG_NAME_WR(REG_INTR1, reg);
|
|
|
|
reg = MHL_SII_REG_NAME_RD(REG_INTR4);
|
|
MHL_SII_REG_NAME_WR(REG_INTR4, reg);
|
|
|
|
/* disable INTR1 and INTR4 */
|
|
MHL_SII_REG_NAME_MOD(REG_INTR1_MASK, BIT6, 0x0);
|
|
MHL_SII_REG_NAME_MOD(REG_INTR4_MASK,
|
|
(BIT0 | BIT1 | BIT2 | BIT3 | BIT4 | BIT5 | BIT6), 0x0);
|
|
|
|
MHL_SII_PAGE1_MOD(0x003D, BIT0, 0x00);
|
|
spin_lock_irqsave(&mhl_ctrl->lock, flags);
|
|
mhl_ctrl->tx_powered_off = true;
|
|
spin_unlock_irqrestore(&mhl_ctrl->lock, flags);
|
|
pr_debug("%s: disabled\n", __func__);
|
|
disable_irq_nosync(client->irq);
|
|
}
|
|
|
|
static void mhl_hpd_stat_isr(struct mhl_tx_ctrl *mhl_ctrl)
|
|
{
|
|
uint8_t intr_1_stat, cbus_stat, t;
|
|
unsigned long flags;
|
|
struct i2c_client *client = mhl_ctrl->i2c_handle;
|
|
|
|
if (!is_mhl_powered(mhl_ctrl))
|
|
return;
|
|
|
|
/* INTR STATUS 1 */
|
|
intr_1_stat = MHL_SII_PAGE0_RD(0x0071);
|
|
|
|
if (!intr_1_stat)
|
|
return;
|
|
|
|
/* Clear interrupts */
|
|
MHL_SII_PAGE0_WR(0x0071, intr_1_stat);
|
|
|
|
if (BIT6 & intr_1_stat) {
|
|
/*
|
|
* HPD status change event is pending
|
|
* Read CBUS HPD status for this info
|
|
* MSC REQ ABRT REASON
|
|
*/
|
|
cbus_stat = MHL_SII_CBUS_RD(0x0D);
|
|
pr_debug("%s: cbus_stat=[0x%02x] cur_pwr=[%u]\n",
|
|
__func__, cbus_stat, mhl_ctrl->cur_state);
|
|
|
|
spin_lock_irqsave(&mhl_ctrl->lock, flags);
|
|
t = mhl_ctrl->dwnstream_hpd;
|
|
pr_debug("%s: %u: dwnstrm_hpd=0x%02x\n",
|
|
__func__, __LINE__, mhl_ctrl->dwnstream_hpd);
|
|
spin_unlock_irqrestore(&mhl_ctrl->lock, flags);
|
|
|
|
if (BIT6 & (cbus_stat ^ t)) {
|
|
u8 status = cbus_stat & BIT6;
|
|
mhl_drive_hpd(mhl_ctrl, status ? HPD_UP : HPD_DOWN);
|
|
if (!status && mhl_ctrl->mhl_det_discon) {
|
|
pr_debug("%s:%u: power_down\n",
|
|
__func__, __LINE__);
|
|
mhl_tx_down(mhl_ctrl);
|
|
}
|
|
spin_lock_irqsave(&mhl_ctrl->lock, flags);
|
|
mhl_ctrl->dwnstream_hpd = cbus_stat;
|
|
pr_debug("%s: %u: dwnstrm_hpd=0x%02x\n",
|
|
__func__, __LINE__, mhl_ctrl->dwnstream_hpd);
|
|
spin_unlock_irqrestore(&mhl_ctrl->lock, flags);
|
|
mhl_ctrl->mhl_det_discon = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void mhl_sii_cbus_process_errors(struct i2c_client *client,
|
|
u8 int_status)
|
|
{
|
|
u8 abort_reason = 0;
|
|
|
|
if (int_status & BIT2) {
|
|
abort_reason = MHL_SII_REG_NAME_RD(REG_DDC_ABORT_REASON);
|
|
pr_debug("%s: CBUS DDC Abort Reason(0x%02x)\n",
|
|
__func__, abort_reason);
|
|
}
|
|
if (int_status & BIT5) {
|
|
abort_reason = MHL_SII_REG_NAME_RD(REG_PRI_XFR_ABORT_REASON);
|
|
pr_debug("%s: CBUS MSC Requestor Abort Reason(0x%02x)\n",
|
|
__func__, abort_reason);
|
|
MHL_SII_REG_NAME_WR(REG_PRI_XFR_ABORT_REASON, 0xFF);
|
|
}
|
|
if (int_status & BIT6) {
|
|
abort_reason = MHL_SII_REG_NAME_RD(
|
|
REG_CBUS_PRI_FWR_ABORT_REASON);
|
|
pr_debug("%s: CBUS MSC Responder Abort Reason(0x%02x)\n",
|
|
__func__, abort_reason);
|
|
MHL_SII_REG_NAME_WR(REG_CBUS_PRI_FWR_ABORT_REASON, 0xFF);
|
|
}
|
|
}
|
|
|
|
int mhl_send_msc_command(struct mhl_tx_ctrl *mhl_ctrl,
|
|
struct msc_command_struct *req)
|
|
{
|
|
int timeout;
|
|
u8 start_bit = 0x00;
|
|
u8 *burst_data;
|
|
int i;
|
|
struct i2c_client *client = mhl_ctrl->i2c_handle;
|
|
|
|
if (mhl_ctrl->cur_state != POWER_STATE_D0_MHL) {
|
|
pr_debug("%s: power_state:%02x CBUS(0x0A):%02x\n",
|
|
__func__,
|
|
mhl_ctrl->cur_state,
|
|
MHL_SII_REG_NAME_RD(REG_CBUS_BUS_STATUS));
|
|
return -EFAULT;
|
|
}
|
|
|
|
if (!req)
|
|
return -EFAULT;
|
|
|
|
pr_debug("%s: command=0x%02x offset=0x%02x %02x %02x",
|
|
__func__,
|
|
req->command,
|
|
req->offset,
|
|
req->payload.data[0],
|
|
req->payload.data[1]);
|
|
|
|
/* REG_CBUS_PRI_ADDR_CMD = REQ CBUS CMD or OFFSET */
|
|
MHL_SII_REG_NAME_WR(REG_CBUS_PRI_ADDR_CMD, req->offset);
|
|
MHL_SII_REG_NAME_WR(REG_CBUS_PRI_WR_DATA_1ST,
|
|
req->payload.data[0]);
|
|
|
|
switch (req->command) {
|
|
case MHL_SET_INT:
|
|
case MHL_WRITE_STAT:
|
|
start_bit = MSC_START_BIT_WRITE_REG;
|
|
break;
|
|
case MHL_READ_DEVCAP:
|
|
start_bit = MSC_START_BIT_READ_REG;
|
|
break;
|
|
case MHL_GET_STATE:
|
|
case MHL_GET_VENDOR_ID:
|
|
case MHL_SET_HPD:
|
|
case MHL_CLR_HPD:
|
|
case MHL_GET_SC1_ERRORCODE:
|
|
case MHL_GET_DDC_ERRORCODE:
|
|
case MHL_GET_MSC_ERRORCODE:
|
|
case MHL_GET_SC3_ERRORCODE:
|
|
start_bit = MSC_START_BIT_MSC_CMD;
|
|
MHL_SII_REG_NAME_WR(REG_CBUS_PRI_ADDR_CMD, req->command);
|
|
break;
|
|
case MHL_MSC_MSG:
|
|
start_bit = MSC_START_BIT_VS_CMD;
|
|
MHL_SII_REG_NAME_WR(REG_CBUS_PRI_WR_DATA_2ND,
|
|
req->payload.data[1]);
|
|
MHL_SII_REG_NAME_WR(REG_CBUS_PRI_ADDR_CMD, req->command);
|
|
break;
|
|
case MHL_WRITE_BURST:
|
|
start_bit = MSC_START_BIT_WRITE_BURST;
|
|
MHL_SII_REG_NAME_WR(REG_MSC_WRITE_BURST_LEN, req->length - 1);
|
|
if (!(req->payload.burst_data)) {
|
|
pr_err("%s: burst data is null!\n", __func__);
|
|
goto cbus_send_fail;
|
|
}
|
|
burst_data = req->payload.burst_data;
|
|
for (i = 0; i < req->length; i++, burst_data++)
|
|
MHL_SII_REG_NAME_WR(REG_CBUS_SCRATCHPAD_0 + i,
|
|
*burst_data);
|
|
break;
|
|
default:
|
|
pr_err("%s: unknown command! (%02x)\n",
|
|
__func__, req->command);
|
|
goto cbus_send_fail;
|
|
}
|
|
|
|
INIT_COMPLETION(mhl_ctrl->msc_cmd_done);
|
|
MHL_SII_REG_NAME_WR(REG_CBUS_PRI_START, start_bit);
|
|
timeout = wait_for_completion_timeout
|
|
(&mhl_ctrl->msc_cmd_done, msecs_to_jiffies(T_ABORT_NEXT));
|
|
if (!timeout) {
|
|
pr_err("%s: cbus_command_send timed out!\n", __func__);
|
|
goto cbus_send_fail;
|
|
}
|
|
|
|
switch (req->command) {
|
|
case MHL_READ_DEVCAP:
|
|
req->retval = MHL_SII_REG_NAME_RD(REG_CBUS_PRI_RD_DATA_1ST);
|
|
break;
|
|
case MHL_MSC_MSG:
|
|
/* check if MSC_MSG NACKed */
|
|
if (MHL_SII_REG_NAME_RD(REG_MSC_WRITE_BURST_LEN) & BIT6)
|
|
return -EAGAIN;
|
|
default:
|
|
req->retval = 0;
|
|
break;
|
|
}
|
|
mhl_msc_command_done(mhl_ctrl, req);
|
|
pr_debug("%s: msc cmd done\n", __func__);
|
|
return 0;
|
|
|
|
cbus_send_fail:
|
|
return -EFAULT;
|
|
}
|
|
|
|
/* read scratchpad */
|
|
void mhl_read_scratchpad(struct mhl_tx_ctrl *mhl_ctrl)
|
|
{
|
|
struct i2c_client *client = mhl_ctrl->i2c_handle;
|
|
int i;
|
|
|
|
for (i = 0; i < MHL_SCRATCHPAD_SIZE; i++) {
|
|
mhl_ctrl->scrpd.data[i] = MHL_SII_REG_NAME_RD(
|
|
REG_CBUS_SCRATCHPAD_0 + i);
|
|
}
|
|
}
|
|
|
|
static void mhl_cbus_isr(struct mhl_tx_ctrl *mhl_ctrl)
|
|
{
|
|
uint8_t regval;
|
|
int req_done = 0;
|
|
uint8_t sub_cmd = 0x0;
|
|
uint8_t cmd_data = 0x0;
|
|
int msc_msg_recved = 0;
|
|
int rc = -1;
|
|
unsigned long flags;
|
|
struct i2c_client *client = mhl_ctrl->i2c_handle;
|
|
|
|
regval = MHL_SII_REG_NAME_RD(REG_CBUS_INTR_STATUS);
|
|
if (regval == 0xff)
|
|
return;
|
|
|
|
if (regval)
|
|
MHL_SII_REG_NAME_WR(REG_CBUS_INTR_STATUS, regval);
|
|
|
|
pr_debug("%s: CBUS_INT = %02x\n", __func__, regval);
|
|
|
|
/* MSC_MSG (RCP/RAP) */
|
|
if (regval & BIT3) {
|
|
sub_cmd = MHL_SII_REG_NAME_RD(REG_CBUS_PRI_VS_CMD);
|
|
cmd_data = MHL_SII_REG_NAME_RD(REG_CBUS_PRI_VS_DATA);
|
|
msc_msg_recved = 1;
|
|
}
|
|
/* MSC_MT_ABRT/MSC_MR_ABRT/DDC_ABORT */
|
|
if (regval & (BIT6 | BIT5 | BIT2))
|
|
mhl_sii_cbus_process_errors(client, regval);
|
|
|
|
/* MSC_REQ_DONE */
|
|
if (regval & BIT4)
|
|
req_done = 1;
|
|
|
|
/* look for interrupts on CBUS_MSC_INT2 */
|
|
regval = MHL_SII_REG_NAME_RD(REG_CBUS_MSC_INT2_STATUS);
|
|
|
|
/* clear all interrupts */
|
|
if (regval)
|
|
MHL_SII_REG_NAME_WR(REG_CBUS_MSC_INT2_STATUS, regval);
|
|
|
|
pr_debug("%s: CBUS_MSC_INT2 = %02x\n", __func__, regval);
|
|
|
|
/* received SET_INT */
|
|
if (regval & BIT2) {
|
|
uint8_t intr;
|
|
intr = MHL_SII_REG_NAME_RD(REG_CBUS_SET_INT_0);
|
|
MHL_SII_REG_NAME_WR(REG_CBUS_SET_INT_0, intr);
|
|
mhl_msc_recv_set_int(mhl_ctrl, 0, intr);
|
|
if (intr & MHL_INT_DCAP_CHG) {
|
|
/* No need to go to low power mode */
|
|
spin_lock_irqsave(&mhl_ctrl->lock, flags);
|
|
mhl_ctrl->dwnstream_hpd = 0x00;
|
|
pr_debug("%s: %u: dwnstrm_hpd=0x%02x\n",
|
|
__func__, __LINE__, mhl_ctrl->dwnstream_hpd);
|
|
spin_unlock_irqrestore(&mhl_ctrl->lock, flags);
|
|
}
|
|
|
|
pr_debug("%s: MHL_INT_0 = %02x\n", __func__, intr);
|
|
intr = MHL_SII_REG_NAME_RD(REG_CBUS_SET_INT_1);
|
|
MHL_SII_REG_NAME_WR(REG_CBUS_SET_INT_1, intr);
|
|
mhl_msc_recv_set_int(mhl_ctrl, 1, intr);
|
|
|
|
pr_debug("%s: MHL_INT_1 = %02x\n", __func__, intr);
|
|
MHL_SII_REG_NAME_WR(REG_CBUS_SET_INT_2, 0xFF);
|
|
MHL_SII_REG_NAME_WR(REG_CBUS_SET_INT_3, 0xFF);
|
|
}
|
|
|
|
/* received WRITE_STAT */
|
|
if (regval & BIT3) {
|
|
uint8_t stat;
|
|
stat = MHL_SII_REG_NAME_RD(REG_CBUS_WRITE_STAT_0);
|
|
mhl_msc_recv_write_stat(mhl_ctrl, 0, stat);
|
|
|
|
pr_debug("%s: MHL_STATUS_0 = %02x\n", __func__, stat);
|
|
stat = MHL_SII_REG_NAME_RD(REG_CBUS_WRITE_STAT_1);
|
|
mhl_msc_recv_write_stat(mhl_ctrl, 1, stat);
|
|
pr_debug("%s: MHL_STATUS_1 = %02x\n", __func__, stat);
|
|
|
|
MHL_SII_REG_NAME_WR(REG_CBUS_WRITE_STAT_0, 0xFF);
|
|
MHL_SII_REG_NAME_WR(REG_CBUS_WRITE_STAT_1, 0xFF);
|
|
MHL_SII_REG_NAME_WR(REG_CBUS_WRITE_STAT_2, 0xFF);
|
|
MHL_SII_REG_NAME_WR(REG_CBUS_WRITE_STAT_3, 0xFF);
|
|
}
|
|
|
|
/* received MSC_MSG */
|
|
if (msc_msg_recved) {
|
|
/*mhl msc recv msc msg*/
|
|
rc = mhl_msc_recv_msc_msg(mhl_ctrl, sub_cmd, cmd_data);
|
|
if (rc)
|
|
pr_err("MHL: mhl msc recv msc msg failed(%d)!\n", rc);
|
|
}
|
|
/* complete last command */
|
|
if (req_done)
|
|
complete_all(&mhl_ctrl->msc_cmd_done);
|
|
|
|
}
|
|
|
|
static irqreturn_t mhl_tx_isr(int irq, void *data)
|
|
{
|
|
int rc;
|
|
struct mhl_tx_ctrl *mhl_ctrl = (struct mhl_tx_ctrl *)data;
|
|
unsigned long flags;
|
|
|
|
pr_debug("%s: Getting Interrupts\n", __func__);
|
|
|
|
spin_lock_irqsave(&mhl_ctrl->lock, flags);
|
|
if (mhl_ctrl->tx_powered_off) {
|
|
pr_warn("%s: powered off\n", __func__);
|
|
spin_unlock_irqrestore(&mhl_ctrl->lock, flags);
|
|
return IRQ_HANDLED;
|
|
}
|
|
spin_unlock_irqrestore(&mhl_ctrl->lock, flags);
|
|
|
|
/*
|
|
* Check RGND, MHL_EST, CBUS_LOCKOUT, SCDT
|
|
* interrupts. In D3, we get only RGND
|
|
*/
|
|
rc = dev_detect_isr(mhl_ctrl);
|
|
if (rc)
|
|
pr_debug("%s: dev_detect_isr rc=[%d]\n", __func__, rc);
|
|
|
|
pr_debug("%s: cur pwr state is [0x%x]\n",
|
|
__func__, mhl_ctrl->cur_state);
|
|
|
|
/*
|
|
* If dev_detect_isr() didn't move the tx to D3
|
|
* on disconnect, continue to check other
|
|
* interrupt sources.
|
|
*/
|
|
mhl_misc_isr(mhl_ctrl);
|
|
|
|
/*
|
|
* Check for any peer messages for DCAP_CHG, MSC etc
|
|
* Dispatch to have the CBUS module working only
|
|
* once connected.
|
|
*/
|
|
mhl_cbus_isr(mhl_ctrl);
|
|
mhl_hpd_stat_isr(mhl_ctrl);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
|
|
static int mhl_sii_reg_config(struct i2c_client *client, bool enable)
|
|
{
|
|
static struct regulator *reg_8941_l24;
|
|
static struct regulator *reg_8941_l02;
|
|
static struct regulator *reg_8941_smps3a;
|
|
static struct regulator *reg_8941_vdda;
|
|
int rc = -EINVAL;
|
|
|
|
pr_debug("%s\n", __func__);
|
|
|
|
if (!enable) {
|
|
if (reg_8941_vdda) {
|
|
regulator_disable(reg_8941_vdda);
|
|
regulator_put(reg_8941_vdda);
|
|
reg_8941_vdda = NULL;
|
|
}
|
|
|
|
if (reg_8941_smps3a) {
|
|
regulator_disable(reg_8941_smps3a);
|
|
regulator_put(reg_8941_smps3a);
|
|
reg_8941_smps3a = NULL;
|
|
}
|
|
|
|
if (reg_8941_l02) {
|
|
regulator_disable(reg_8941_l02);
|
|
regulator_put(reg_8941_l02);
|
|
reg_8941_l02 = NULL;
|
|
}
|
|
|
|
if (reg_8941_l24) {
|
|
regulator_disable(reg_8941_l24);
|
|
regulator_put(reg_8941_l24);
|
|
reg_8941_l24 = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (!reg_8941_l24) {
|
|
reg_8941_l24 = regulator_get(&client->dev,
|
|
"avcc_18");
|
|
if (IS_ERR(reg_8941_l24)) {
|
|
pr_err("could not get 8941 l24, rc = %ld\n",
|
|
PTR_ERR(reg_8941_l24));
|
|
return -ENODEV;
|
|
}
|
|
if (enable)
|
|
rc = regulator_enable(reg_8941_l24);
|
|
else
|
|
rc = regulator_disable(reg_8941_l24);
|
|
if (rc) {
|
|
pr_err("'%s' regulator config[%u] failed, rc=%d\n",
|
|
"avcc_1.8V", enable, rc);
|
|
goto l24_fail;
|
|
} else {
|
|
pr_debug("%s: vreg L24 %s\n",
|
|
__func__, (enable ? "enabled" : "disabled"));
|
|
}
|
|
}
|
|
|
|
if (!reg_8941_l02) {
|
|
reg_8941_l02 = regulator_get(&client->dev,
|
|
"avcc_12");
|
|
if (IS_ERR(reg_8941_l02)) {
|
|
pr_err("could not get reg_8941_l02, rc = %ld\n",
|
|
PTR_ERR(reg_8941_l02));
|
|
goto l24_fail;
|
|
}
|
|
if (enable)
|
|
rc = regulator_enable(reg_8941_l02);
|
|
else
|
|
rc = regulator_disable(reg_8941_l02);
|
|
if (rc) {
|
|
pr_debug("'%s' regulator configure[%u] failed, rc=%d\n",
|
|
"avcc_1.2V", enable, rc);
|
|
goto l02_fail;
|
|
} else {
|
|
pr_debug("%s: vreg L02 %s\n",
|
|
__func__, (enable ? "enabled" : "disabled"));
|
|
}
|
|
}
|
|
|
|
if (!reg_8941_smps3a) {
|
|
reg_8941_smps3a = regulator_get(&client->dev,
|
|
"smps3a");
|
|
if (IS_ERR(reg_8941_smps3a)) {
|
|
pr_err("could not get vreg smps3a, rc = %ld\n",
|
|
PTR_ERR(reg_8941_smps3a));
|
|
goto l02_fail;
|
|
}
|
|
if (enable)
|
|
rc = regulator_enable(reg_8941_smps3a);
|
|
else
|
|
rc = regulator_disable(reg_8941_smps3a);
|
|
if (rc) {
|
|
pr_err("'%s' regulator config[%u] failed, rc=%d\n",
|
|
"SMPS3A", enable, rc);
|
|
goto smps3a_fail;
|
|
} else {
|
|
pr_debug("%s: vreg SMPS3A %s\n",
|
|
__func__, (enable ? "enabled" : "disabled"));
|
|
}
|
|
}
|
|
|
|
if (!reg_8941_vdda) {
|
|
reg_8941_vdda = regulator_get(&client->dev,
|
|
"vdda");
|
|
if (IS_ERR(reg_8941_vdda)) {
|
|
pr_err("could not get vreg vdda, rc = %ld\n",
|
|
PTR_ERR(reg_8941_vdda));
|
|
goto smps3a_fail;
|
|
}
|
|
if (enable)
|
|
rc = regulator_enable(reg_8941_vdda);
|
|
else
|
|
rc = regulator_disable(reg_8941_vdda);
|
|
if (rc) {
|
|
pr_err("'%s' regulator config[%u] failed, rc=%d\n",
|
|
"VDDA", enable, rc);
|
|
goto vdda_fail;
|
|
} else {
|
|
pr_debug("%s: vreg VDDA %s\n",
|
|
__func__, (enable ? "enabled" : "disabled"));
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
|
|
vdda_fail:
|
|
regulator_disable(reg_8941_vdda);
|
|
regulator_put(reg_8941_vdda);
|
|
smps3a_fail:
|
|
regulator_disable(reg_8941_smps3a);
|
|
regulator_put(reg_8941_smps3a);
|
|
l02_fail:
|
|
regulator_disable(reg_8941_l02);
|
|
regulator_put(reg_8941_l02);
|
|
l24_fail:
|
|
regulator_disable(reg_8941_l24);
|
|
regulator_put(reg_8941_l24);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
|
|
static int mhl_vreg_config(struct mhl_tx_ctrl *mhl_ctrl, uint8_t on)
|
|
{
|
|
int ret;
|
|
struct i2c_client *client = mhl_ctrl->i2c_handle;
|
|
int pwr_gpio = mhl_ctrl->pdata->gpios[MHL_TX_PMIC_PWR_GPIO]->gpio;
|
|
|
|
pr_debug("%s\n", __func__);
|
|
if (on) {
|
|
ret = gpio_request(pwr_gpio,
|
|
mhl_ctrl->pdata->gpios[MHL_TX_PMIC_PWR_GPIO]->gpio_name);
|
|
if (ret < 0) {
|
|
pr_err("%s: mhl pwr gpio req failed: %d\n",
|
|
__func__, ret);
|
|
return ret;
|
|
}
|
|
ret = gpio_direction_output(pwr_gpio, 1);
|
|
if (ret < 0) {
|
|
pr_err("%s: set gpio MHL_PWR_EN dircn failed: %d\n",
|
|
__func__, ret);
|
|
goto vreg_config_failed;
|
|
}
|
|
|
|
ret = mhl_sii_reg_config(client, true);
|
|
if (ret) {
|
|
pr_err("%s: regulator enable failed\n", __func__);
|
|
goto vreg_config_failed;
|
|
}
|
|
pr_debug("%s: mhl sii power on successful\n", __func__);
|
|
} else {
|
|
pr_warn("%s: turning off pwr controls\n", __func__);
|
|
mhl_sii_reg_config(client, false);
|
|
gpio_free(pwr_gpio);
|
|
}
|
|
pr_debug("%s: successful\n", __func__);
|
|
return 0;
|
|
vreg_config_failed:
|
|
gpio_free(pwr_gpio);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Request for GPIO allocations
|
|
* Set appropriate GPIO directions
|
|
*/
|
|
static int mhl_gpio_config(struct mhl_tx_ctrl *mhl_ctrl, int on)
|
|
{
|
|
int ret;
|
|
struct dss_gpio *temp_reset_gpio, *temp_intr_gpio;
|
|
|
|
/* caused too many line spills */
|
|
temp_reset_gpio = mhl_ctrl->pdata->gpios[MHL_TX_RESET_GPIO];
|
|
temp_intr_gpio = mhl_ctrl->pdata->gpios[MHL_TX_INTR_GPIO];
|
|
|
|
if (on) {
|
|
if (gpio_is_valid(temp_reset_gpio->gpio)) {
|
|
ret = gpio_request(temp_reset_gpio->gpio,
|
|
temp_reset_gpio->gpio_name);
|
|
if (ret < 0) {
|
|
pr_err("%s:rst_gpio=[%d] req failed:%d\n",
|
|
__func__, temp_reset_gpio->gpio, ret);
|
|
return -EBUSY;
|
|
}
|
|
ret = gpio_direction_output(temp_reset_gpio->gpio, 0);
|
|
if (ret < 0) {
|
|
pr_err("%s: set dirn rst failed: %d\n",
|
|
__func__, ret);
|
|
return -EBUSY;
|
|
}
|
|
}
|
|
if (gpio_is_valid(temp_intr_gpio->gpio)) {
|
|
ret = gpio_request(temp_intr_gpio->gpio,
|
|
temp_intr_gpio->gpio_name);
|
|
if (ret < 0) {
|
|
pr_err("%s: intr_gpio req failed: %d\n",
|
|
__func__, ret);
|
|
return -EBUSY;
|
|
}
|
|
ret = gpio_direction_input(temp_intr_gpio->gpio);
|
|
if (ret < 0) {
|
|
pr_err("%s: set dirn intr failed: %d\n",
|
|
__func__, ret);
|
|
return -EBUSY;
|
|
}
|
|
mhl_ctrl->i2c_handle->irq = gpio_to_irq(
|
|
temp_intr_gpio->gpio);
|
|
pr_debug("%s: gpio_to_irq=%d\n",
|
|
__func__, mhl_ctrl->i2c_handle->irq);
|
|
}
|
|
} else {
|
|
pr_warn("%s: freeing gpios\n", __func__);
|
|
gpio_free(temp_intr_gpio->gpio);
|
|
gpio_free(temp_reset_gpio->gpio);
|
|
}
|
|
pr_debug("%s: successful\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static int mhl_i2c_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
int rc = 0;
|
|
struct mhl_tx_platform_data *pdata = NULL;
|
|
struct mhl_tx_ctrl *mhl_ctrl;
|
|
struct usb_ext_notification *mhl_info = NULL;
|
|
struct msm_hdmi_mhl_ops *hdmi_mhl_ops = NULL;
|
|
|
|
mhl_ctrl = devm_kzalloc(&client->dev, sizeof(*mhl_ctrl), GFP_KERNEL);
|
|
if (!mhl_ctrl) {
|
|
pr_err("%s: FAILED: cannot alloc hdmi tx ctrl\n", __func__);
|
|
rc = -ENOMEM;
|
|
goto failed_no_mem;
|
|
}
|
|
|
|
if (client->dev.of_node) {
|
|
pdata = devm_kzalloc(&client->dev,
|
|
sizeof(struct mhl_tx_platform_data), GFP_KERNEL);
|
|
if (!pdata) {
|
|
dev_err(&client->dev, "Failed to allocate memory\n");
|
|
rc = -ENOMEM;
|
|
goto failed_no_mem;
|
|
}
|
|
|
|
rc = mhl_tx_get_dt_data(&client->dev, pdata);
|
|
if (rc) {
|
|
pr_err("%s: FAILED: parsing device tree data; rc=%d\n",
|
|
__func__, rc);
|
|
goto failed_dt_data;
|
|
}
|
|
mhl_ctrl->i2c_handle = client;
|
|
mhl_ctrl->pdata = pdata;
|
|
i2c_set_clientdata(client, mhl_ctrl);
|
|
}
|
|
|
|
/*
|
|
* Other initializations
|
|
* such tx specific
|
|
*/
|
|
mhl_ctrl->disc_enabled = false;
|
|
INIT_WORK(&mhl_ctrl->mhl_msc_send_work, mhl_msc_send_work);
|
|
mhl_ctrl->cur_state = POWER_STATE_D0_MHL;
|
|
INIT_LIST_HEAD(&mhl_ctrl->list_cmd);
|
|
init_completion(&mhl_ctrl->msc_cmd_done);
|
|
spin_lock_init(&mhl_ctrl->lock);
|
|
mhl_ctrl->msc_send_workqueue = create_singlethread_workqueue
|
|
("mhl_msc_cmd_queue");
|
|
mhl_ctrl->mhl_workq = create_singlethread_workqueue("mhl_workq");
|
|
|
|
INIT_WORK(&mhl_ctrl->mhl_intr_work, mhl_sii_disc_intr_work);
|
|
|
|
mhl_ctrl->input = input_allocate_device();
|
|
if (mhl_ctrl->input) {
|
|
int i;
|
|
struct input_dev *input = mhl_ctrl->input;
|
|
|
|
mhl_ctrl->rcp_key_code_tbl = vmalloc(
|
|
sizeof(support_rcp_key_code_tbl));
|
|
if (!mhl_ctrl->rcp_key_code_tbl) {
|
|
pr_err("%s: no alloc mem for rcp keycode tbl\n",
|
|
__func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
mhl_ctrl->rcp_key_code_tbl_len = sizeof(
|
|
support_rcp_key_code_tbl);
|
|
memcpy(mhl_ctrl->rcp_key_code_tbl,
|
|
&support_rcp_key_code_tbl[0],
|
|
mhl_ctrl->rcp_key_code_tbl_len);
|
|
|
|
input->phys = "cbus/input0";
|
|
input->id.bustype = BUS_VIRTUAL;
|
|
input->id.vendor = 0x1095;
|
|
input->id.product = 0x8334;
|
|
input->id.version = 0xA;
|
|
|
|
input->name = "mhl-rcp";
|
|
|
|
input->keycode = support_rcp_key_code_tbl;
|
|
input->keycodesize = sizeof(u16);
|
|
input->keycodemax = ARRAY_SIZE(support_rcp_key_code_tbl);
|
|
|
|
input->evbit[0] = EV_KEY;
|
|
for (i = 0; i < ARRAY_SIZE(support_rcp_key_code_tbl); i++) {
|
|
if (support_rcp_key_code_tbl[i] > 1)
|
|
input_set_capability(input, EV_KEY,
|
|
support_rcp_key_code_tbl[i]);
|
|
}
|
|
|
|
if (input_register_device(input) < 0) {
|
|
pr_warn("%s: failed to register input device\n",
|
|
__func__);
|
|
input_free_device(input);
|
|
mhl_ctrl->input = NULL;
|
|
}
|
|
}
|
|
|
|
mhl_ctrl->dwnstream_hpd = 0;
|
|
mhl_ctrl->tx_powered_off = false;
|
|
|
|
|
|
init_completion(&mhl_ctrl->rgnd_done);
|
|
mutex_init(&mhl_ctrl->sii_config_lock);
|
|
|
|
|
|
mhl_ctrl->mhl_psy.name = "ext-vbus";
|
|
mhl_ctrl->mhl_psy.type = POWER_SUPPLY_TYPE_USB_DCP;
|
|
mhl_ctrl->mhl_psy.supplied_to = mhl_pm_power_supplied_to;
|
|
mhl_ctrl->mhl_psy.num_supplicants = ARRAY_SIZE(
|
|
mhl_pm_power_supplied_to);
|
|
mhl_ctrl->mhl_psy.properties = mhl_pm_power_props;
|
|
mhl_ctrl->mhl_psy.num_properties = ARRAY_SIZE(mhl_pm_power_props);
|
|
mhl_ctrl->mhl_psy.get_property = mhl_power_get_property;
|
|
mhl_ctrl->mhl_psy.set_property = mhl_power_set_property;
|
|
|
|
rc = power_supply_register(&client->dev, &mhl_ctrl->mhl_psy);
|
|
if (rc < 0) {
|
|
dev_err(&client->dev, "%s:power_supply_register ext_vbus_psy failed\n",
|
|
__func__);
|
|
goto failed_probe;
|
|
}
|
|
|
|
hdmi_mhl_ops = devm_kzalloc(&client->dev,
|
|
sizeof(struct msm_hdmi_mhl_ops),
|
|
GFP_KERNEL);
|
|
if (!hdmi_mhl_ops) {
|
|
pr_err("%s: alloc hdmi mhl ops failed\n", __func__);
|
|
rc = -ENOMEM;
|
|
goto failed_probe_pwr;
|
|
}
|
|
|
|
pr_debug("%s: i2c client addr is [%x]\n", __func__, client->addr);
|
|
if (mhl_ctrl->pdata->hdmi_pdev) {
|
|
rc = msm_hdmi_register_mhl(mhl_ctrl->pdata->hdmi_pdev,
|
|
hdmi_mhl_ops, mhl_ctrl);
|
|
if (rc) {
|
|
pr_err("%s: register with hdmi failed\n", __func__);
|
|
rc = -EPROBE_DEFER;
|
|
goto failed_probe_pwr;
|
|
}
|
|
}
|
|
|
|
if (!hdmi_mhl_ops || !hdmi_mhl_ops->tmds_enabled ||
|
|
!hdmi_mhl_ops->set_mhl_max_pclk) {
|
|
pr_err("%s: func ptr is NULL\n", __func__);
|
|
rc = -EINVAL;
|
|
goto failed_probe_pwr;
|
|
}
|
|
mhl_ctrl->hdmi_mhl_ops = hdmi_mhl_ops;
|
|
|
|
rc = hdmi_mhl_ops->set_mhl_max_pclk(
|
|
mhl_ctrl->pdata->hdmi_pdev, MAX_MHL_PCLK);
|
|
if (rc) {
|
|
pr_err("%s: can't set max mhl pclk\n", __func__);
|
|
goto failed_probe_pwr;
|
|
}
|
|
|
|
mhl_info = devm_kzalloc(&client->dev, sizeof(*mhl_info), GFP_KERNEL);
|
|
if (!mhl_info) {
|
|
pr_err("%s: alloc mhl info failed\n", __func__);
|
|
rc = -ENOMEM;
|
|
goto failed_probe_pwr;
|
|
}
|
|
|
|
mhl_info->ctxt = mhl_ctrl;
|
|
mhl_info->notify = mhl_sii_device_discovery;
|
|
if (msm_register_usb_ext_notification(mhl_info)) {
|
|
pr_err("%s: register for usb notifcn failed\n", __func__);
|
|
rc = -EPROBE_DEFER;
|
|
goto failed_probe_pwr;
|
|
}
|
|
mhl_ctrl->mhl_info = mhl_info;
|
|
mhl_register_msc(mhl_ctrl);
|
|
return 0;
|
|
|
|
failed_probe_pwr:
|
|
power_supply_unregister(&mhl_ctrl->mhl_psy);
|
|
failed_probe:
|
|
mhl_sii_config(mhl_ctrl, false);
|
|
/* do not deep-free */
|
|
if (mhl_info)
|
|
devm_kfree(&client->dev, mhl_info);
|
|
failed_dt_data:
|
|
if (pdata)
|
|
devm_kfree(&client->dev, pdata);
|
|
failed_no_mem:
|
|
if (mhl_ctrl)
|
|
devm_kfree(&client->dev, mhl_ctrl);
|
|
mhl_info = NULL;
|
|
pdata = NULL;
|
|
mhl_ctrl = NULL;
|
|
pr_err("%s: PROBE FAILED, rc=%d\n", __func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
|
|
static int mhl_i2c_remove(struct i2c_client *client)
|
|
{
|
|
struct mhl_tx_ctrl *mhl_ctrl = i2c_get_clientdata(client);
|
|
|
|
if (!mhl_ctrl) {
|
|
pr_warn("%s: i2c get client data failed\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mhl_sii_config(mhl_ctrl, false);
|
|
|
|
destroy_workqueue(mhl_ctrl->mhl_workq);
|
|
|
|
if (mhl_ctrl->mhl_info)
|
|
devm_kfree(&client->dev, mhl_ctrl->mhl_info);
|
|
if (mhl_ctrl->pdata)
|
|
devm_kfree(&client->dev, mhl_ctrl->pdata);
|
|
devm_kfree(&client->dev, mhl_ctrl);
|
|
return 0;
|
|
}
|
|
|
|
static struct i2c_device_id mhl_sii_i2c_id[] = {
|
|
{ MHL_DRIVER_NAME, 0 },
|
|
{ }
|
|
};
|
|
|
|
|
|
MODULE_DEVICE_TABLE(i2c, mhl_sii_i2c_id);
|
|
|
|
#if defined(CONFIG_PM) || defined(CONFIG_PM_SLEEP)
|
|
static int mhl_i2c_suspend_sub(struct i2c_client *client)
|
|
{
|
|
struct mhl_tx_ctrl *mhl_ctrl = i2c_get_clientdata(client);
|
|
|
|
pr_debug("%s\n", __func__);
|
|
|
|
if (!mhl_ctrl) {
|
|
pr_err("%s: invalid ctrl data\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
if (mhl_ctrl->mhl_mode) {
|
|
mhl_ctrl->mhl_mode = 0;
|
|
|
|
power_supply_changed(&mhl_ctrl->mhl_psy);
|
|
if (mhl_ctrl->notify_usb_online)
|
|
mhl_ctrl->notify_usb_online(mhl_ctrl->notify_ctx, 0);
|
|
|
|
mhl_sii_config(mhl_ctrl, false);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mhl_i2c_resume_sub(struct i2c_client *client)
|
|
{
|
|
pr_debug("%s\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
#endif /* defined(CONFIG_PM) || defined(CONFIG_PM_SLEEP) */
|
|
|
|
#if defined(CONFIG_PM) && !defined(CONFIG_PM_SLEEP)
|
|
static int mhl_i2c_suspend(struct i2c_client *client, pm_message_t state)
|
|
{
|
|
if (!client)
|
|
return -ENODEV;
|
|
pr_debug("%s: mhl suspend\n", __func__);
|
|
return mhl_i2c_suspend_sub(client);
|
|
}
|
|
|
|
static int mhl_i2c_resume(struct i2c_client *client)
|
|
{
|
|
if (!client)
|
|
return -ENODEV;
|
|
pr_debug("%s: mhl resume\n", __func__);
|
|
return mhl_i2c_resume_sub(client);
|
|
}
|
|
#else
|
|
#define mhl_i2c_suspend NULL
|
|
#define mhl_i2c_resume NULL
|
|
#endif /* defined(CONFIG_PM) && !defined(CONFIG_PM_SLEEP) */
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int mhl_i2c_pm_suspend(struct device *dev)
|
|
{
|
|
struct i2c_client *client =
|
|
container_of(dev, struct i2c_client, dev);
|
|
|
|
if (!client)
|
|
return -ENODEV;
|
|
pr_debug("%s: mhl pm suspend\n", __func__);
|
|
return mhl_i2c_suspend_sub(client);
|
|
|
|
}
|
|
|
|
static int mhl_i2c_pm_resume(struct device *dev)
|
|
{
|
|
struct i2c_client *client =
|
|
container_of(dev, struct i2c_client, dev);
|
|
|
|
if (!client)
|
|
return -ENODEV;
|
|
pr_debug("%s: mhl pm resume\n", __func__);
|
|
return mhl_i2c_resume_sub(client);
|
|
}
|
|
|
|
static const struct dev_pm_ops mhl_i2c_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(mhl_i2c_pm_suspend, mhl_i2c_pm_resume)
|
|
};
|
|
#endif /* CONFIG_PM_SLEEP */
|
|
|
|
static struct of_device_id mhl_match_table[] = {
|
|
{.compatible = COMPATIBLE_NAME,},
|
|
{ },
|
|
};
|
|
|
|
static struct i2c_driver mhl_sii_i2c_driver = {
|
|
.driver = {
|
|
.name = MHL_DRIVER_NAME,
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = mhl_match_table,
|
|
#ifdef CONFIG_PM_SLEEP
|
|
.pm = &mhl_i2c_pm_ops,
|
|
#endif /* CONFIG_PM_SLEEP */
|
|
},
|
|
.probe = mhl_i2c_probe,
|
|
.remove = mhl_i2c_remove,
|
|
#if defined(CONFIG_PM) && !defined(CONFIG_PM_SLEEP)
|
|
.suspend = mhl_i2c_suspend,
|
|
.resume = mhl_i2c_resume,
|
|
#endif /* defined(CONFIG_PM) && !defined(CONFIG_PM_SLEEP) */
|
|
.id_table = mhl_sii_i2c_id,
|
|
};
|
|
|
|
module_i2c_driver(mhl_sii_i2c_driver);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("MHL SII 8334 TX Driver");
|