2024-09-09 08:57:42 +00:00
|
|
|
/* Copyright (c) 2012-2014, The Linux Foundation. All rights reserved.
|
2024-09-09 08:52:07 +00:00
|
|
|
*
|
|
|
|
* 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>
|
2024-09-09 08:57:42 +00:00
|
|
|
#include <linux/of_platform.h>
|
2024-09-09 08:52:07 +00:00
|
|
|
#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>
|
2024-09-09 08:57:42 +00:00
|
|
|
#include <linux/mdss_io_util.h>
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
#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);
|
2024-09-09 08:57:42 +00:00
|
|
|
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);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
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;
|
2024-09-09 08:57:42 +00:00
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
pr_debug("%s:%u\n", __func__, __LINE__);
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
if (mhl_ctrl->mhl_mode) {
|
|
|
|
pr_debug("%s: already in mhl mode\n", __func__);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
reinit_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);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (!timeout) {
|
2024-09-09 08:57:42 +00:00
|
|
|
/*
|
|
|
|
* most likely nothing plugged in USB
|
|
|
|
* USB HOST connected or already in USB mode
|
|
|
|
*/
|
2024-09-09 08:52:07 +00:00
|
|
|
pr_warn("%s:%u timedout\n", __func__, __LINE__);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
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);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = mhl_gpio_config(mhl_ctrl, 1);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("%s: gpio init failed [%d]\n",
|
|
|
|
__func__, rc);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
return -ENODEV;
|
|
|
|
} else {
|
|
|
|
mhl_ctrl->irq_req_done = true;
|
|
|
|
}
|
|
|
|
} 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* USB_HANDSHAKING FUNCTIONS */
|
|
|
|
static int mhl_sii_device_discovery(void *data, int id,
|
2024-09-09 08:57:42 +00:00
|
|
|
void (*usb_notify_cb)(void *, int), void *ctx)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!mhl_ctrl->notify_usb_online) {
|
2024-09-09 08:52:07 +00:00
|
|
|
mhl_ctrl->notify_usb_online = usb_notify_cb;
|
2024-09-09 08:57:42 +00:00
|
|
|
mhl_ctrl->notify_ctx = ctx;
|
|
|
|
}
|
|
|
|
|
|
|
|
flush_work(&mhl_ctrl->mhl_intr_work);
|
|
|
|
|
|
|
|
if (!mhl_ctrl->irq_req_done) {
|
|
|
|
rc = mhl_sii_config(mhl_ctrl, true);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("%s: Failed to config vreg/gpio\n", __func__);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* wait for i2c interrupt line to be activated */
|
|
|
|
msleep(100);
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
2024-09-09 08:57:42 +00:00
|
|
|
msleep(100);
|
2024-09-09 08:52:07 +00:00
|
|
|
mhl_init_reg_settings(mhl_ctrl, true);
|
2024-09-09 08:57:42 +00:00
|
|
|
/* 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;
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
} else {
|
|
|
|
if (mhl_ctrl->cur_state == POWER_STATE_D3) {
|
2024-09-09 08:57:42 +00:00
|
|
|
mhl_sii_wait_for_rgnd(mhl_ctrl);
|
2024-09-09 08:52:07 +00:00
|
|
|
} else {
|
|
|
|
/* in MHL mode */
|
|
|
|
pr_debug("%s:%u\n", __func__, __LINE__);
|
|
|
|
}
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
rc = mhl_ctrl->mhl_mode ? 0 : 1;
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
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;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/* 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);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
/*
|
|
|
|
* 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;
|
2024-09-09 08:57:42 +00:00
|
|
|
mhl_ctrl->mhl_mode = 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
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;
|
2024-09-09 08:57:42 +00:00
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
if (on) {
|
|
|
|
MHL_SII_REG_NAME_MOD(REG_TMDS_CCTRL, BIT4, BIT4);
|
2024-09-09 08:57:42 +00:00
|
|
|
mhl_ctrl->tmds_en_state = true;
|
2024-09-09 08:52:07 +00:00
|
|
|
} else {
|
|
|
|
MHL_SII_REG_NAME_MOD(REG_TMDS_CCTRL, BIT4, 0x00);
|
2024-09-09 08:57:42 +00:00
|
|
|
mhl_ctrl->tmds_en_state = false;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2024-09-09 08:57:42 +00:00
|
|
|
mhl_ctrl->notify_usb_online(mhl_ctrl->notify_ctx, 1);
|
2024-09-09 08:52:07 +00:00
|
|
|
} 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);
|
2024-09-09 08:57:42 +00:00
|
|
|
mhl_drive_hpd(mhl_ctrl, HPD_DOWN);
|
2024-09-09 08:52:07 +00:00
|
|
|
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)
|
2024-09-09 08:57:42 +00:00
|
|
|
mhl_ctrl->notify_usb_online(mhl_ctrl->notify_ctx, 0);
|
|
|
|
|
|
|
|
queue_work(mhl_ctrl->mhl_workq, &mhl_ctrl->mhl_intr_work);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
reinit_completion(&mhl_ctrl->msc_cmd_done);
|
2024-09-09 08:52:07 +00:00
|
|
|
MHL_SII_REG_NAME_WR(REG_CBUS_PRI_START, start_bit);
|
2024-09-09 08:57:42 +00:00
|
|
|
timeout = wait_for_completion_timeout
|
2024-09-09 08:52:07 +00:00
|
|
|
(&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;
|
2024-09-09 08:57:42 +00:00
|
|
|
int rc = -EINVAL;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
pr_debug("%s\n", __func__);
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
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");
|
2024-09-09 08:57:42 +00:00
|
|
|
mhl_ctrl->mhl_workq = create_singlethread_workqueue("mhl_workq");
|
|
|
|
|
|
|
|
INIT_WORK(&mhl_ctrl->mhl_intr_work, mhl_sii_disc_intr_work);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
mhl_ctrl->dwnstream_hpd = 0;
|
|
|
|
mhl_ctrl->tx_powered_off = false;
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
init_completion(&mhl_ctrl->rgnd_done);
|
|
|
|
|
|
|
|
|
|
|
|
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:
|
2024-09-09 08:57:42 +00:00
|
|
|
mhl_sii_config(mhl_ctrl, false);
|
2024-09-09 08:52:07 +00:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
mhl_sii_config(mhl_ctrl, false);
|
|
|
|
|
|
|
|
destroy_workqueue(mhl_ctrl->mhl_workq);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
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)
|
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mhl_i2c_resume_sub(struct i2c_client *client)
|
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
pr_debug("%s\n", __func__);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
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");
|