Rtwo/kernel/motorola/sm8550/drivers/extcon/extcon-lt8711uxe2.c
2025-09-30 19:22:48 -05:00

1493 lines
37 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2024-2025, Qualcomm Innovation Center, Inc. All rights reserved.
*
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/proc_fs.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/platform_device.h>
#include <linux/firmware.h>
#include <linux/crc8.h>
#include <sound/soc.h>
#include <linux/of_gpio.h>
#include <linux/extcon-provider.h>
#include <linux/gpio/consumer.h>
#include <linux/usb/usbpd.h>
#include <linux/bitfield.h>
#define WRITE_BUF_MAX_SIZE 128
#define READ_BUF_MAX_SIZE 128
#define BLOCK_ERASE_DELAY_TIME 500
#define WRITE_DATA_DELAY_TIME 1
#define LT8711UXE2_IRQ_USB_HPD BIT(0)
#define LT8711UXE2_IRQ_DP_ALT_MODE_CHANGE BIT(1)
#define LT8711UXE2_IRQ_HDMI_OUTPUT_CHANGE BIT(2)
#define LT8711UXE2_GPIO_HIGH 0
#define LT8711UXE2_GPIO_LOW 1
#define LT8711UXE2_DP_2LANE LT8711UXE2_GPIO_HIGH
#define LT8711UXE2_DP_4LANE LT8711UXE2_GPIO_LOW
#define LT8711UXE2_DP_ALT_ENABLE LT8711UXE2_GPIO_HIGH
#define LT8711UXE2_DP_ALT_DISABLE LT8711UXE2_GPIO_LOW
#define IRQ_TYPE_EQUALS(irq_val, mask) ((irq_val) & (mask))
DECLARE_CRC8_TABLE(lt8711uxe2_crc_table);
enum LT8711UXE2_DATA_ROLE {
LT8711UXE2_DISCONNECTED = 0,
LT8711UXE2_DFP_ATTACHED,
LT8711UXE2_UFP_ATTACHED
};
/* List of detectable cables */
static const unsigned int lt8711uxe2_extcon_cable_with_redriver[] = {
EXTCON_USB,
EXTCON_USB_HOST,
EXTCON_DISP_DP,
EXTCON_MECHANICAL,
EXTCON_NONE,
};
static const unsigned int lt8711uxe2_extcon_cable[] = {
EXTCON_USB,
EXTCON_USB_HOST,
EXTCON_MECHANICAL,
EXTCON_NONE,
};
struct lt8711uxe2_reg_cfg {
u8 reg;
u8 val;
};
enum lt8711uxe2_fw_upgrade_status {
UPDATE_SUCCESS = 0,
UPDATE_RUNNING = 1,
UPDATE_FAILED = 2,
};
struct lt8711uxe2 {
struct extcon_dev *edev;
struct device *dev;
struct i2c_client *i2c_client;
u8 i2c_wbuf[WRITE_BUF_MAX_SIZE];
u8 i2c_rbuf[READ_BUF_MAX_SIZE];
enum lt8711uxe2_fw_upgrade_status fw_status;
struct work_struct irq_work;
struct mutex mutex;
bool usb_ss_support;
bool with_redriver;
bool with_hdmi_switch;
struct extcon_dev *hdmi_sw_edev;
unsigned int chip_fw_version;
unsigned int image_fw_version;
u32 reset_gpio;
u32 irq_gpio;
u32 dp_lane_sel_gpio;
u32 dp_alt_en_gpio;
int irq;
u8 alt_mode;
u32 cc_finished_gpio;
int cc_irq;
bool is_cc_finished;
};
static void lt8711uxe2_check_state(struct lt8711uxe2 *pdata);
static void lt8711uxe2_set_hdmi_switch_state(struct lt8711uxe2 *pdata, bool enabled);
/*
* Write one reg with more values;
* Reg -> value0, value1, value2.
*/
static int lt8711uxe2_write(struct lt8711uxe2 *pdata, u8 reg,
const u8 *buf, int size)
{
struct i2c_client *client = pdata->i2c_client;
struct i2c_msg msg = {
.addr = client->addr,
.flags = 0,
.len = size + 1,
.buf = pdata->i2c_wbuf,
};
pdata->i2c_wbuf[0] = reg;
if (size > (WRITE_BUF_MAX_SIZE - 1)) {
dev_err(pdata->dev, "invalid write buffer size %d\n", size);
return -EINVAL;
}
memcpy(pdata->i2c_wbuf + 1, buf, size);
if (i2c_transfer(client->adapter, &msg, 1) < 1) {
dev_err(pdata->dev, "i2c write failed\n");
return -EIO;
}
return 0;
}
/*
* Write one reg with one value;
* Reg -> value
*/
static int lt8711uxe2_write_byte(struct lt8711uxe2 *pdata, const u8 reg,
u8 value)
{
struct i2c_client *client = pdata->i2c_client;
struct i2c_msg msg = {
.addr = client->addr,
.flags = 0,
.len = 2,
.buf = pdata->i2c_wbuf,
};
memset(pdata->i2c_wbuf, 0, WRITE_BUF_MAX_SIZE);
pdata->i2c_wbuf[0] = reg;
pdata->i2c_wbuf[1] = value;
if (i2c_transfer(client->adapter, &msg, 1) < 1) {
dev_err(pdata->dev, "i2c write failed\n");
return -EIO;
}
return 0;
}
/*
* Write more regs with more values;
* Reg1 -> value1
* Reg2 -> value2
*/
static void lt8711uxe2_write_array(struct lt8711uxe2 *pdata,
struct lt8711uxe2_reg_cfg *reg_arry, int size)
{
int i = 0;
for (i = 0; i < size; i++)
lt8711uxe2_write_byte(pdata, reg_arry[i].reg, reg_arry[i].val);
}
static int lt8711uxe2_read(struct lt8711uxe2 *pdata, u8 reg, char *buf,
u32 size)
{
struct i2c_client *client = pdata->i2c_client;
struct i2c_msg msg[2] = {{
.addr = client->addr,
.flags = 0,
.len = 1,
.buf = pdata->i2c_wbuf,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = size,
.buf = pdata->i2c_rbuf,
}};
if (size > READ_BUF_MAX_SIZE) {
dev_err(pdata->dev, "invalid read buff size %d\n", size);
return -EINVAL;
}
memset(pdata->i2c_wbuf, 0x0, WRITE_BUF_MAX_SIZE);
memset(pdata->i2c_rbuf, 0x0, READ_BUF_MAX_SIZE);
pdata->i2c_wbuf[0] = reg;
if (i2c_transfer(client->adapter, msg, 2) != 2) {
dev_err(pdata->dev, "i2c read failed\n");
return -EIO;
}
memcpy(buf, pdata->i2c_rbuf, size);
return 0;
}
static void lt8711uxe2_ctl_en(struct lt8711uxe2 *pdata)
{
lt8711uxe2_write_byte(pdata, 0xFF, 0xE0);
lt8711uxe2_write_byte(pdata, 0xEE, 0x01);
}
static void lt8711uxe2_ctl_disable(struct lt8711uxe2 *pdata)
{
lt8711uxe2_write_byte(pdata, 0xFF, 0xE0);
lt8711uxe2_write_byte(pdata, 0xEE, 0x00);
}
static int lt8711uxe2_parse_dt(struct lt8711uxe2 *pdata)
{
int ret = 0;
if (of_property_read_u32(pdata->dev->of_node, "img-fw-rev",
&pdata->image_fw_version) < 0) {
dev_err(pdata->dev, "failed reading image firmware version\n");
pdata->image_fw_version = 0;
}
pdata->irq_gpio = of_get_named_gpio(pdata->dev->of_node,
"lt,irq-gpio", 0);
if (!gpio_is_valid(pdata->irq_gpio)) {
dev_err(pdata->dev, "irq gpio not specified\n");
ret = -EINVAL;
} else
pr_debug("irq_gpio=%d\n", pdata->irq_gpio);
pdata->reset_gpio = of_get_named_gpio(pdata->dev->of_node,
"lt,reset-gpio", 0);
if (!gpio_is_valid(pdata->reset_gpio)) {
dev_err(pdata->dev, "reset gpio not specified\n");
ret = -EINVAL;
} else
pr_debug("reset_gpio=%d\n", pdata->reset_gpio);
pdata->dp_lane_sel_gpio = of_get_named_gpio(pdata->dev->of_node,
"lt,dp-lane-sel", 0);
if (!gpio_is_valid(pdata->dp_lane_sel_gpio))
pr_debug("dp_lane_sel gpio not specified\n");
else
pr_debug("dp_lane_sel_gpio=%d\n", pdata->dp_lane_sel_gpio);
pdata->dp_alt_en_gpio = of_get_named_gpio(pdata->dev->of_node,
"lt,dp-alt-en", 0);
if (!gpio_is_valid(pdata->dp_alt_en_gpio))
pr_debug("dp_alt_en gpio not specified\n");
else
pr_debug("dp_alt_en_gpio=%d\n", pdata->dp_alt_en_gpio);
pdata->cc_finished_gpio = of_get_named_gpio(pdata->dev->of_node,
"lt,cc-gpio", 0);
if (!gpio_is_valid(pdata->cc_finished_gpio))
pr_err("cc_finished_gpio not specified\n");
else
pr_debug("cc_finished_gpio=%d\n", pdata->cc_finished_gpio);
return ret;
}
static int lt8711uxe2_gpio_configure(struct lt8711uxe2 *pdata, bool on)
{
int ret = 0;
if (on) {
ret = gpio_request(pdata->irq_gpio, "lt8711uxe2-irq-gpio");
if (ret) {
dev_err(pdata->dev, "lt8711uxe2 irq gpio request failed\n");
goto error;
}
ret = gpio_direction_input(pdata->irq_gpio);
if (ret) {
dev_err(pdata->dev, "lt8711uxe2 irq gpio direction failed\n");
goto irq_err;
}
ret = gpio_request(pdata->reset_gpio, "lt8711uxe2-reset-gpio");
if (ret) {
dev_err(pdata->dev, "lt8711uxe2 reset gpio request failed\n");
goto irq_err;
}
ret = gpio_direction_output(pdata->reset_gpio,
LT8711UXE2_GPIO_HIGH);
if (ret) {
dev_err(pdata->dev, "lt8711uxe2 reset gpio direction failed\n");
goto reset_err;
}
if (gpio_is_valid(pdata->dp_lane_sel_gpio)) {
ret = gpio_request(pdata->dp_lane_sel_gpio,
"lt8711uxe2-dp_lane_sel-gpio");
if (ret) {
dev_err(pdata->dev, "dp_lane_sel gpio request failed\n");
goto reset_err;
}
/* Default select 2 lane DP + 2 lane USB. */
ret = gpio_direction_output(pdata->dp_lane_sel_gpio,
LT8711UXE2_DP_2LANE);
if (ret) {
dev_err(pdata->dev, "dp_lane_sel gpio direction failed\n");
goto dp_lane_sel_err;
}
}
if (gpio_is_valid(pdata->dp_alt_en_gpio)) {
ret = gpio_request(pdata->dp_alt_en_gpio,
"lt8711uxe2-dp_alt_en-gpio");
if (ret) {
dev_err(pdata->dev, "dp_alt_en gpio request failed\n");
goto dp_lane_sel_err;
}
/* Default enable DP alternate mode. */
ret = gpio_direction_output(pdata->dp_alt_en_gpio,
LT8711UXE2_DP_ALT_ENABLE);
if (ret) {
dev_err(pdata->dev, "dp_alt_en gpio direction failed\n");
goto dp_alt_en_err;
}
}
if (gpio_is_valid(pdata->cc_finished_gpio)) {
ret = gpio_request(pdata->cc_finished_gpio, "lt8711uxe2-cc-gpio");
if (ret) {
dev_err(pdata->dev, "lt8711uxe2 cc gpio request failed\n");
goto dp_alt_en_err;
}
ret = gpio_direction_input(pdata->cc_finished_gpio);
if (ret) {
dev_err(pdata->dev, "lt8711uxe2 cc gpio direction failed\n");
goto cc_finished_err;
}
}
} else {
gpio_free(pdata->cc_finished_gpio);
if (gpio_is_valid(pdata->dp_lane_sel_gpio))
gpio_free(pdata->dp_alt_en_gpio);
if (gpio_is_valid(pdata->dp_alt_en_gpio))
gpio_free(pdata->dp_lane_sel_gpio);
if (gpio_is_valid(pdata->reset_gpio))
gpio_free(pdata->reset_gpio);
gpio_free(pdata->irq_gpio);
}
return ret;
cc_finished_err:
if (gpio_is_valid(pdata->cc_finished_gpio))
gpio_free(pdata->cc_finished_gpio);
dp_alt_en_err:
if (gpio_is_valid(pdata->dp_lane_sel_gpio))
gpio_free(pdata->dp_alt_en_gpio);
dp_lane_sel_err:
if (gpio_is_valid(pdata->dp_alt_en_gpio))
gpio_free(pdata->dp_lane_sel_gpio);
reset_err:
gpio_free(pdata->reset_gpio);
irq_err:
gpio_free(pdata->irq_gpio);
error:
return ret;
}
static void lt8711uxe2_reset(struct lt8711uxe2 *pdata, bool on_off)
{
pr_debug("reset: %d\n", on_off);
mutex_lock(&pdata->mutex);
if (on_off) {
gpio_set_value(pdata->reset_gpio, LT8711UXE2_GPIO_HIGH);
pr_debug("reset GPIO_HIGH\n");
msleep(20);
gpio_set_value(pdata->reset_gpio, LT8711UXE2_GPIO_LOW);
pr_debug("reset GPIO_LOW\n");
msleep(100);
gpio_set_value(pdata->reset_gpio, LT8711UXE2_GPIO_HIGH);
pr_debug("reset GPIO_HIGH\n");
/* Need longer time to make sure LT8711UXE2 initialized. */
msleep(500);
} else
gpio_set_value(pdata->reset_gpio, LT8711UXE2_GPIO_HIGH);
mutex_unlock(&pdata->mutex);
}
static int lt8711uxe2_read_firmware_version(struct lt8711uxe2 *pdata)
{
u8 rev[3] = {};
int ret = 0;
memset(rev, 0x0, 3);
lt8711uxe2_ctl_en(pdata);
lt8711uxe2_write_byte(pdata, 0xFF, 0xE0);
ret = lt8711uxe2_read(pdata, 0x81, rev, 3);
if (ret == 0) {
pdata->chip_fw_version = ((rev[0] << 16) |
(rev[1] << 8) | rev[2]);
dev_info(pdata->dev, "Firmware version: 0x%x\n", pdata->chip_fw_version);
} else
dev_err(pdata->dev, "read Firmware version fail\n");
lt8711uxe2_ctl_disable(pdata);
return ret;
}
static int lt8711uxe2_read_alt_mode(struct lt8711uxe2 *pdata)
{
u8 alt_mode_stat = 0;
int ret = 0;
lt8711uxe2_write_byte(pdata, 0xFF, 0xE0);
ret = lt8711uxe2_read(pdata, 0x85, &alt_mode_stat, 1);
if (ret == 0) {
pdata->alt_mode = alt_mode_stat;
pr_debug("alt mode:%d\n", pdata->alt_mode);
} else
dev_err(pdata->dev, "read alt mode fail\n");
return ret;
}
static void lt8711uxe2_config(struct lt8711uxe2 *pdata)
{
struct lt8711uxe2_reg_cfg reg_cfg[] = {
{ 0xFF, 0xE0 }, { 0xEE, 0x01 }, { 0x5E, 0xC1 }, { 0x58, 0x00 },
{ 0x59, 0x50 }, { 0x5A, 0x10 }, { 0x5A, 0x00 }, { 0x58, 0x21 },
};
lt8711uxe2_write_array(pdata, reg_cfg, ARRAY_SIZE(reg_cfg));
}
static void lt8711uxe2_flash_write_en(struct lt8711uxe2 *pdata)
{
struct lt8711uxe2_reg_cfg reg_cfg0[] = {
{ 0xFF, 0xE1 },
{ 0x03, 0x3F },
};
struct lt8711uxe2_reg_cfg reg_cfg1[] = {
{ 0xFF, 0xE0 },
{ 0x5A, 0x04 },
{ 0x5A, 0x00 },
};
lt8711uxe2_write_array(pdata, reg_cfg0, ARRAY_SIZE(reg_cfg0));
msleep(WRITE_DATA_DELAY_TIME);
lt8711uxe2_write_byte(pdata, 0x03, 0xFF);
msleep(WRITE_DATA_DELAY_TIME);
lt8711uxe2_write_array(pdata, reg_cfg1, ARRAY_SIZE(reg_cfg1));
}
static void lt8711uxe2_flash_write_di(struct lt8711uxe2 *pdata)
{
struct lt8711uxe2_reg_cfg reg_cfg0[] = {
{ 0x5A, 0x08 },
{ 0x5A, 0x00 },
};
lt8711uxe2_write_array(pdata, reg_cfg0, ARRAY_SIZE(reg_cfg0));
}
static void lt8711uxe2_block_erase(struct lt8711uxe2 *pdata, u32 addr)
{
struct lt8711uxe2_reg_cfg reg_cfg[] = {
{ 0xFF, 0xE0 },
{ 0xEE, 0x01 },
{ 0x5A, 0x04 },
{ 0x5A, 0x00 },
{ 0x5B, (addr & 0xFF0000) >> 16 },
{ 0x5C, (addr & 0xFF00) >> 8 },
{ 0x5D, addr & 0xFF },
{ 0x5A, 0x01 },
{ 0x5A, 0x00 },
};
pr_debug("block erase\n");
lt8711uxe2_write_array(pdata, reg_cfg, ARRAY_SIZE(reg_cfg));
msleep(BLOCK_ERASE_DELAY_TIME);
}
static void lt8711uxe2_flash_read_addr_set(struct lt8711uxe2 *pdata, u32 addr)
{
struct lt8711uxe2_reg_cfg reg_cfg[] = {
{ 0x5E, 0x5F },
{ 0x5A, 0x20 },
{ 0x5A, 0x00 },
{ 0x5B, (addr & 0xFF0000) >> 16 },
{ 0x5C, (addr & 0xFF00) >> 8 },
{ 0x5D, addr & 0xFF },
{ 0x5A, 0x10 },
{ 0x5A, 0x00 },
{ 0x58, 0x21 },
};
lt8711uxe2_write_array(pdata, reg_cfg, ARRAY_SIZE(reg_cfg));
}
static void lt8711uxe2_fw_read_back(struct lt8711uxe2 *pdata, u8 *buff,
int size, u32 addr)
{
u8 page_data[32];
int page_number = 0, i = 0;
/*
* Read 32 bytes once.
*/
page_number = size / 32;
if (size % 32)
page_number++;
for (i = 0; i < page_number; i++) {
memset(page_data, 0x0, 32);
lt8711uxe2_flash_read_addr_set(pdata, addr);
lt8711uxe2_read(pdata, 0x5F, page_data, 32);
memcpy(buff, page_data, 32);
buff += 32;
addr += 32;
pr_debug("fw read page: %d\n", i);
}
}
static void lt8711uxe2_flash_write_config(struct lt8711uxe2 *pdata)
{
struct lt8711uxe2_reg_cfg reg_cfg[] = {
{ 0x5E, 0xDF },
{ 0x5A, 0x20 },
{ 0x5A, 0x00 },
{ 0x58, 0x21 },
};
lt8711uxe2_flash_write_en(pdata);
lt8711uxe2_write_array(pdata, reg_cfg, ARRAY_SIZE(reg_cfg));
}
static void lt8711uxe2_flash_write_addr_set(struct lt8711uxe2 *pdata, u32 addr)
{
struct lt8711uxe2_reg_cfg reg_cfg[] = {
{ 0x5B, (u8)((addr & 0xFF0000) >> 16) },
{ 0x5C, (u8)((addr & 0xFF00) >> 8) },
{ 0x5D, addr & 0xFF },
{ 0x5A, 0x10 },
{ 0x5A, 0x00 },
};
lt8711uxe2_write_array(pdata, reg_cfg, ARRAY_SIZE(reg_cfg));
}
static int lt8711uxe2_calculate_crc8(const u8 *fdata, int size)
{
u8 crc = 0;
u16 block_size = 0x8000;
u8 *crc_data = NULL;
crc_data = kzalloc(block_size, GFP_KERNEL);
if (!crc_data)
return -ENOMEM;
memcpy(crc_data, fdata, size);
memset(crc_data + size, 0xff, block_size - size);
crc = crc8(lt8711uxe2_crc_table, crc_data, block_size - 1, crc);
kfree(crc_data);
return crc;
}
static void lt8711uxe2_fw_write(struct lt8711uxe2 *pdata,
const u8 *fdata, int size, u32 addr)
{
u8 last_buf[32];
int i = 0, page_size = 32;
int total_page = 0, rest_data = 0;
int start_addr = addr;
u32 crc_addr = ((addr & 0xFF0000) | 0x007FFF);
const u8 *fdata_start_addr = fdata;
int crc8;
u8 crc_buf[2];
total_page = size / page_size;
rest_data = size % page_size;
for (i = 0; i < total_page; i++) {
lt8711uxe2_flash_write_config(pdata);
lt8711uxe2_write(pdata, 0x59, fdata, page_size);
pr_debug("firmware write page: %d\n", i);
lt8711uxe2_flash_write_addr_set(pdata, start_addr);
start_addr += page_size;
fdata += page_size;
msleep(WRITE_DATA_DELAY_TIME);
}
if (rest_data > 0) {
memset(last_buf, 0xFF, 32);
memcpy(last_buf, fdata, rest_data);
lt8711uxe2_flash_write_config(pdata);
lt8711uxe2_write(pdata, 0x59, last_buf, page_size);
pr_debug("firmware write page: %d\n", i);
lt8711uxe2_flash_write_addr_set(pdata, start_addr);
msleep(WRITE_DATA_DELAY_TIME);
}
/* write crc8 */
memset(crc_buf, 0xFF, 2);
crc8 = lt8711uxe2_calculate_crc8(fdata_start_addr, size);
if (crc8 == -ENOMEM)
return;
crc_buf[0] = crc8;
lt8711uxe2_flash_write_config(pdata);
lt8711uxe2_write(pdata, 0x59, crc_buf, 1);
lt8711uxe2_flash_write_addr_set(pdata, crc_addr);
pr_debug("crc8 addr: 0x%06x, crc8 data: 0x%02x\n",
crc_addr, crc_buf[0]);
msleep(WRITE_DATA_DELAY_TIME);
pr_debug("FW write over, total size: %d, page: %d, reset: %d\n", size,
total_page, rest_data);
}
static void lt8711uxe2_fw_upgrade(struct lt8711uxe2 *pdata,
const struct firmware *cfg, u32 addr)
{
int i = 0;
int cfg_crc8, fw_crc8;
u8 *fw_read_data = NULL;
int data_len = (int)cfg->size;
pr_debug("FW total size %d\n", data_len);
fw_read_data = kzalloc(ALIGN(data_len, 32), GFP_KERNEL);
if (!fw_read_data)
return;
pdata->fw_status = UPDATE_RUNNING;
lt8711uxe2_config(pdata);
/*
* Need erase block 2 timess here.
* Sometimes, erase can fail.
* This is a workaroud.
*/
for (i = 0; i < 2; i++)
lt8711uxe2_block_erase(pdata, addr);
lt8711uxe2_fw_write(pdata, cfg->data, data_len, addr);
msleep(WRITE_DATA_DELAY_TIME);
lt8711uxe2_fw_read_back(pdata, fw_read_data, data_len, addr);
cfg_crc8 = lt8711uxe2_calculate_crc8(cfg->data, data_len);
fw_crc8 = lt8711uxe2_calculate_crc8(fw_read_data, data_len);
pr_debug("crc8 calculated using F/W bin file: 0x%02x\n",
cfg_crc8);
pr_debug("crc8 calculated by reading from LT8711uxe: 0x%02x\n",
fw_crc8);
if (cfg_crc8 == -ENOMEM || fw_crc8 == -ENOMEM) {
kfree(fw_read_data);
return;
}
if (cfg_crc8 == fw_crc8)
pr_debug("check crc8 pass\n");
else
dev_err(pdata->dev, "check crc8 error\n");
if (!memcmp(cfg->data, fw_read_data, data_len)) {
pdata->fw_status = UPDATE_SUCCESS;
pr_debug("firmware upgrade success.\n");
lt8711uxe2_reset(pdata, true);
} else {
pdata->fw_status = UPDATE_FAILED;
dev_err(pdata->dev, "firmware upgrade failed\n");
}
kfree(fw_read_data);
}
static void lt8711uxe2_fw_cb_main(const struct firmware *cfg, void *data)
{
struct lt8711uxe2 *pdata = (struct lt8711uxe2 *)data;
u32 addr = 0x000000;
if (!cfg) {
dev_err(pdata->dev, "get firmware failed\n");
return;
}
lt8711uxe2_fw_upgrade(pdata, cfg, addr);
release_firmware(cfg);
}
static void lt8711uxe2_fw_cb_backup(const struct firmware *cfg,
void *data)
{
struct lt8711uxe2 *pdata = (struct lt8711uxe2 *)data;
u32 addr = 0x040000;
if (!cfg) {
dev_err(pdata->dev, "get firmware failed\n");
return;
}
lt8711uxe2_fw_upgrade(pdata, cfg, addr);
release_firmware(cfg);
}
static void lt8711uxe2_fw_debug_write_main_fw(const struct firmware *cfg,
void *data)
{
struct lt8711uxe2 *pdata = (struct lt8711uxe2 *)data;
int data_len = (int)cfg->size;
u32 addr = 0x000000;
if (!cfg) {
dev_err(pdata->dev, "get firmware failed\n");
return;
}
/* only write main fw */
lt8711uxe2_config(pdata);
lt8711uxe2_fw_write(pdata, cfg->data, data_len, addr);
lt8711uxe2_flash_write_di(pdata);
lt8711uxe2_ctl_disable(pdata);
release_firmware(cfg);
}
static void lt8711uxe2_fw_debug_write_backup_fw(const struct firmware *cfg,
void *data)
{
struct lt8711uxe2 *pdata = (struct lt8711uxe2 *)data;
int data_len = (int)cfg->size;
u32 addr = 0x040000;
if (!cfg) {
dev_err(pdata->dev, "get firmware failed\n");
return;
}
/* only write backup fw */
lt8711uxe2_config(pdata);
lt8711uxe2_fw_write(pdata, cfg->data, data_len, addr);
lt8711uxe2_flash_write_di(pdata);
lt8711uxe2_ctl_disable(pdata);
release_firmware(cfg);
}
static void lt8711uxe2_fw_debug_crc8_main_fw(struct lt8711uxe2 *pdata)
{
int ret = 0;
u8 *fw_read_data = NULL;
u32 addr = 0x000000;
int block_size = 0x8000;
u8 crc8;
pr_debug("get main F/W crc8...\n");
fw_read_data = kzalloc(ALIGN(block_size, 32), GFP_KERNEL);
if (!fw_read_data)
return;
/* read main fw */
lt8711uxe2_config(pdata);
lt8711uxe2_fw_read_back(pdata, fw_read_data, block_size, addr);
lt8711uxe2_flash_write_di(pdata);
lt8711uxe2_ctl_disable(pdata);
/* read chip calculate crc8 */
lt8711uxe2_ctl_en(pdata);
lt8711uxe2_write_byte(pdata, 0xFF, 0xE0);
ret = lt8711uxe2_read(pdata, 0x21, &crc8, 1);
if (ret == 0)
pr_debug("read crc8 register data: 0x%02x\n",
crc8);
else
dev_info(pdata->dev, "read crc8 register fail\n");
lt8711uxe2_ctl_disable(pdata);
/* get f/w block last byte (crc8) */
pr_debug("read flash last byte (crc8 data): 0x%02x\n",
fw_read_data[block_size - 1]);
/* use f/w data to calculate crc8 */
pr_debug("calculate crc8 data: 0x%02x\n",
lt8711uxe2_calculate_crc8(fw_read_data, block_size - 1));
kfree(fw_read_data);
}
static void lt8711uxe2_fw_debug_crc8_backup_fw(struct lt8711uxe2 *pdata)
{
int ret = 0;
u8 *fw_read_data = NULL;
u32 addr = 0x040000;
int block_size = 0x8000;
u8 crc8;
pr_debug("get backup F/W crc8...\n");
fw_read_data = kzalloc(ALIGN(block_size, 32), GFP_KERNEL);
if (!fw_read_data)
return;
/* read backup fw */
lt8711uxe2_config(pdata);
lt8711uxe2_fw_read_back(pdata, fw_read_data, block_size, addr);
lt8711uxe2_flash_write_di(pdata);
lt8711uxe2_ctl_disable(pdata);
/* read chip calculate crc8 */
lt8711uxe2_ctl_en(pdata);
lt8711uxe2_write_byte(pdata, 0xFF, 0xE0);
ret = lt8711uxe2_read(pdata, 0x21, &crc8, 1);
if (ret == 0)
pr_debug("read crc8 register data: 0x%02x\n",
crc8);
else
dev_info(pdata->dev, "read crc8 register fail\n");
lt8711uxe2_ctl_disable(pdata);
/* get f/w block last byte (crc8) */
pr_debug("read flash last byte (crc8 data): 0x%02x\n",
fw_read_data[block_size - 1]);
/* use f/w data to calculate crc8 */
pr_debug("calculate crc8 data: 0x%02x\n",
lt8711uxe2_calculate_crc8(fw_read_data, block_size - 1));
kfree(fw_read_data);
}
static void lt8711uxe2_fw_debug_crc8_bin(const struct firmware *cfg,
void *data)
{
int data_len = (int)cfg->size;
if (!cfg) {
pr_err("get firmware failed\n");
return;
}
/* print F/W bin file crc8 */
pr_debug("get firmware bin file crc8: 0x%02x\n",
lt8711uxe2_calculate_crc8(cfg->data, data_len));
release_firmware(cfg);
}
static ssize_t firmware_upgrade_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct lt8711uxe2 *pdata = dev_get_drvdata(dev);
int ret = 0;
int get;
u32 addr;
int i = 0;
if (!pdata)
return -EINVAL;
if (pdata->fw_status == UPDATE_RUNNING) {
dev_err(dev, "lt8711uxe2 bridge upgrade is already in progress\n");
return -EINVAL;
}
sscanf(buf, "%d", &get);
switch (get) {
case 0:
/* Upgrade Main F/W SOP */
ret = request_firmware_nowait(THIS_MODULE, true,
"lt8711uxe2_fw.bin",
&pdata->i2c_client->dev,
GFP_KERNEL, pdata,
lt8711uxe2_fw_cb_main);
if (ret)
dev_err(dev, "Failed to invoke firmware loader: %d\n", ret);
else
dev_info(dev, "main F/W starts upgrading, wait for 70s\n");
break;
case 1:
/* Upgrade Backup F/W SOP */
ret = request_firmware_nowait(THIS_MODULE, true,
"lt8711uxe2_fw.bin",
&pdata->i2c_client->dev,
GFP_KERNEL, pdata,
lt8711uxe2_fw_cb_backup);
if (ret)
dev_err(dev, "Failed to invoke firmware loader: %d\n", ret);
else
dev_info(dev, "backup F/W starts upgrading, wait for 70s\n");
break;
case 2:
/*Erase Main F/W Block */
addr = 0x000000;
lt8711uxe2_config(pdata);
for (i = 0; i < 2; i++)
lt8711uxe2_block_erase(pdata, addr);
lt8711uxe2_ctl_disable(pdata);
break;
case 3:
/*Erase Backup F/W Block */
addr = 0x040000;
lt8711uxe2_config(pdata);
for (i = 0; i < 2; i++)
lt8711uxe2_block_erase(pdata, addr);
lt8711uxe2_ctl_disable(pdata);
break;
case 4:
/* Write Main F/W Block */
ret = request_firmware_nowait(
THIS_MODULE, true, "lt8711uxe2_fw.bin",
&pdata->i2c_client->dev, GFP_KERNEL, pdata,
lt8711uxe2_fw_debug_write_main_fw);
if (ret)
dev_err(dev, "Failed to invoke firmware loader: %d\n", ret);
else
dev_info(dev, "main F/W starts writing, wait for 50s...\n");
break;
case 5:
/* Write Backup F/W Block */
ret = request_firmware_nowait(
THIS_MODULE, true, "lt8711uxe2_fw.bin",
&pdata->i2c_client->dev, GFP_KERNEL, pdata,
lt8711uxe2_fw_debug_write_backup_fw);
if (ret)
dev_err(dev, "Failed to invoke firmware loader: %d\n", ret);
else
dev_info(dev, "backup F/W starts writing, wait for 50s...\n");
break;
case 6:
/* Get Main F/W Block CRC8 */
lt8711uxe2_fw_debug_crc8_main_fw(pdata);
break;
case 7:
/* Get Backup F/W Block CRC8 */
lt8711uxe2_fw_debug_crc8_backup_fw(pdata);
break;
case 8:
/* Calculate F/W Bin File CRC8 */
ret = request_firmware_nowait(
THIS_MODULE, true, "lt8711uxe2_fw.bin",
&pdata->i2c_client->dev, GFP_KERNEL, pdata,
lt8711uxe2_fw_debug_crc8_bin);
if (ret)
dev_err(dev, "Failed to invoke firmware loader: %d\n", ret);
else
dev_info(dev, "F/W bin file crc8 calculating...\n");
break;
default:
break;
}
return ret ? : count;
}
static ssize_t firmware_upgrade_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct lt8711uxe2 *pdata = dev_get_drvdata(dev);
return scnprintf(buf, PAGE_SIZE, "%d\n", pdata->fw_status); //scnprintf
}
static ssize_t get_fw_version_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct lt8711uxe2 *pdata = dev_get_drvdata(dev);
if (pdata->fw_status == UPDATE_RUNNING) {
dev_err(dev, "can't check firmware while upgrading bridge\n");
return -EINVAL;
}
lt8711uxe2_read_firmware_version(pdata);
return scnprintf(buf, PAGE_SIZE, "%#x\n", pdata->chip_fw_version);
}
static ssize_t dp_alt_en_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int get;
struct lt8711uxe2 *pdata = dev_get_drvdata(dev);
if (kstrtoint(buf, 0, &get))
return -EINVAL;
if (pdata->dp_alt_en_gpio) {
if (get != 0) {
gpio_set_value(pdata->dp_alt_en_gpio,
LT8711UXE2_DP_ALT_ENABLE);
} else {
gpio_set_value(pdata->dp_alt_en_gpio,
LT8711UXE2_DP_ALT_DISABLE);
}
} else
dev_err(dev, "Invalid gpio %#x!\n", pdata->dp_alt_en_gpio);
return count;
}
static ssize_t dp_alt_en_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct lt8711uxe2 *pdata = dev_get_drvdata(dev);
if (!pdata->dp_alt_en_gpio)
return scnprintf(buf, PAGE_SIZE, "Invalid gpio\n");
if (gpio_get_value(pdata->dp_alt_en_gpio) == LT8711UXE2_DP_ALT_ENABLE)
return scnprintf(buf, PAGE_SIZE, "%s\n", "Enabled");
else
return scnprintf(buf, PAGE_SIZE, "%s\n", "Disabled");
}
static ssize_t dp_lane_sel_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int get;
struct lt8711uxe2 *pdata = dev_get_drvdata(dev);
if (kstrtoint(buf, 0, &get))
return -EINVAL;
if (pdata->dp_lane_sel_gpio) {
switch (get) {
case 4:
dev_err(dev, "Select 4 lane DP with USB 2.0\n");
gpio_set_value(pdata->dp_lane_sel_gpio,
LT8711UXE2_DP_4LANE);
break;
case 2:
dev_err(dev, "Select 2 lane DP with USB 3.0\n");
gpio_set_value(pdata->dp_lane_sel_gpio,
LT8711UXE2_DP_2LANE);
break;
default:
dev_warn(dev, "invalid, default select 2 lane DP\n");
gpio_set_value(pdata->dp_lane_sel_gpio,
LT8711UXE2_DP_2LANE);
break;
}
} else
dev_err(dev, "Invalid gpio %#x!\n", pdata->dp_lane_sel_gpio);
return count;
}
static ssize_t dp_lane_sel_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct lt8711uxe2 *pdata = dev_get_drvdata(dev);
if (!pdata->dp_lane_sel_gpio)
return scnprintf(buf, PAGE_SIZE, "Invalid gpio\n");
if (gpio_get_value(pdata->dp_lane_sel_gpio) == LT8711UXE2_DP_2LANE)
return scnprintf(buf, PAGE_SIZE, "%s\n",
"2 Lane DP with USB 3.0");
else
return scnprintf(buf, PAGE_SIZE, "%s\n",
"4 Lane DP with USB 2.0");
}
static ssize_t get_alt_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct lt8711uxe2 *pdata = dev_get_drvdata(dev);
return scnprintf(buf, PAGE_SIZE, "%d\n", pdata->alt_mode & BIT(0));
}
/* sysfs entries */
static DEVICE_ATTR_RW(firmware_upgrade);
static DEVICE_ATTR_RO(get_fw_version);
static DEVICE_ATTR_RW(dp_alt_en);
static DEVICE_ATTR_RW(dp_lane_sel);
static DEVICE_ATTR_RO(get_alt_mode);
static struct attribute *lt8711uxe2_sysfs_attrs[] = {
&dev_attr_firmware_upgrade.attr,
&dev_attr_get_fw_version.attr,
&dev_attr_dp_alt_en.attr,
&dev_attr_dp_lane_sel.attr,
&dev_attr_get_alt_mode.attr,
NULL,
};
static struct attribute_group lt8711uxe2_attr_group = {
.attrs = lt8711uxe2_sysfs_attrs,
};
static void lt8711uxe2_check_state(struct lt8711uxe2 *pdata)
{
u8 data_role = 0;
union extcon_property_value flip;
union extcon_property_value ss_func;
u8 flip_reg_val = 0;
bool host_mode = false;
bool device_mode = false;
bool connected = false;
bool flipped = false;
unsigned int extcon_id = EXTCON_NONE;
u32 dp_lane = gpio_get_value(pdata->dp_lane_sel_gpio);
mutex_lock(&pdata->mutex);
lt8711uxe2_write_byte(pdata, 0xFF, 0xE0);
lt8711uxe2_read(pdata, 0x84, &data_role, 1);
lt8711uxe2_read(pdata, 0x85, &flip_reg_val, 1);
mutex_unlock(&pdata->mutex);
flipped = !!(flip_reg_val & BIT(2));
pr_debug("%s flipped = %s\n", __func__, (flipped ? "True" : "False"));
switch (data_role) {
case LT8711UXE2_DISCONNECTED:
pr_debug("%s LT8711UXE2_DISCONNECTED\n", __func__);
host_mode = false;
device_mode = false;
connected = false;
break;
case LT8711UXE2_DFP_ATTACHED:
pr_debug("%s LT8711UXE2_DFP_ATTACH (device mode)\n", __func__);
extcon_id = EXTCON_USB;
host_mode = false;
device_mode = true;
connected = true;
break;
case LT8711UXE2_UFP_ATTACHED:
pr_debug("%s LT8711UXE2_UFP_ATTACH (host mode)\n", __func__);
extcon_id = EXTCON_USB_HOST;
host_mode = true;
device_mode = false;
connected = true;
break;
default:
dev_err(pdata->dev, "Unknown state: %#x\n", data_role);
return;
}
if (gpio_is_valid(pdata->cc_finished_gpio) &&
(!pdata->is_cc_finished)) {
pr_err("cc communicate not finish wait it!\n");
return;
}
extcon_set_state(pdata->edev, EXTCON_USB_HOST, host_mode);
extcon_set_state(pdata->edev, EXTCON_USB, device_mode);
if (pdata->usb_ss_support) {
if (dp_lane == LT8711UXE2_DP_2LANE)
ss_func.intval = 1;
else
ss_func.intval = 0;
extcon_set_property(pdata->edev, extcon_id,
EXTCON_PROP_USB_SS, ss_func);
if (pdata->with_redriver)
extcon_set_property(pdata->edev, EXTCON_DISP_DP,
EXTCON_PROP_USB_SS, ss_func);
}
flip.intval = 0;
extcon_set_property(pdata->edev, extcon_id,
EXTCON_PROP_USB_TYPEC_POLARITY, flip);
if (pdata->with_redriver) {
flip.intval = flipped;
extcon_set_property(pdata->edev, EXTCON_DISP_DP,
EXTCON_PROP_USB_TYPEC_POLARITY, flip);
extcon_set_state(pdata->edev, EXTCON_DISP_DP, true);
extcon_sync(pdata->edev, EXTCON_DISP_DP);
}
extcon_sync(pdata->edev, EXTCON_USB);
extcon_sync(pdata->edev, EXTCON_USB_HOST);
}
static void lt8711uxe2_set_hdmi_switch_state(struct lt8711uxe2 *pdata,
bool enabled)
{
if (pdata->with_hdmi_switch) {
extcon_set_state(pdata->edev, EXTCON_MECHANICAL,
enabled);
extcon_sync(pdata->edev, EXTCON_MECHANICAL);
}
}
static irqreturn_t lt8711uxe2_irq_thread_handler(int irq, void *dev_id)
{
struct lt8711uxe2 *pdata = (struct lt8711uxe2 *)dev_id;
u8 irq_type = 0;
u8 alt_mode_stat = 0;
if (!pdata)
return IRQ_HANDLED;
if (pdata->fw_status == UPDATE_RUNNING) {
dev_err(pdata->dev, "UPDATE_RUNNING abnormal irq!\n");
return IRQ_HANDLED;
}
mutex_lock(&pdata->mutex);
lt8711uxe2_write_byte(pdata, 0xFF, 0xE0);
lt8711uxe2_read(pdata, 0x80, &irq_type, 1);
mutex_unlock(&pdata->mutex);
if (IRQ_TYPE_EQUALS(irq_type, LT8711UXE2_IRQ_USB_HPD))
lt8711uxe2_check_state(pdata);
if (IRQ_TYPE_EQUALS(irq_type, LT8711UXE2_IRQ_DP_ALT_MODE_CHANGE)) {
lt8711uxe2_read(pdata, 0x85, &alt_mode_stat, 1);
pdata->alt_mode = alt_mode_stat;
if (alt_mode_stat & BIT(0))
pr_debug("Enter to DP alt mode\n");
else
pr_debug("Exit DP alt mode.\n");
}
if (IRQ_TYPE_EQUALS(irq_type, LT8711UXE2_IRQ_HDMI_OUTPUT_CHANGE)) {
lt8711uxe2_read(pdata, 0x85, &alt_mode_stat, 1);
pdata->alt_mode = alt_mode_stat;
lt8711uxe2_set_hdmi_switch_state(pdata,
!!(alt_mode_stat & BIT(0)));
if (alt_mode_stat & BIT(1))
pr_debug("HDMI output enabled.\n");
else
pr_debug("HDMI output disabled.\n");
}
return IRQ_HANDLED;
}
static irqreturn_t lt8711uxe2_cc_irq_thread_handler(int irq, void *dev_id)
{
struct lt8711uxe2 *pdata = (struct lt8711uxe2 *)dev_id;
if (gpio_get_value(pdata->cc_finished_gpio)) {
pdata->is_cc_finished = true;
lt8711uxe2_reset(pdata, true);
pr_debug("get cc gpio high\n");
} else {
gpio_set_value(pdata->reset_gpio, LT8711UXE2_GPIO_LOW);
pr_debug("get cc gpio low\n");
}
return IRQ_HANDLED;
}
static int lt8711uxe2_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct lt8711uxe2 *pdata;
int ret = 0;
pdata = devm_kzalloc(&client->dev,
sizeof(struct lt8711uxe2), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
pdata->dev = &client->dev;
pdata->i2c_client = client;
i2c_set_clientdata(client, pdata);
/* create crc8 table */
crc8_populate_msb(lt8711uxe2_crc_table, 0x31);
ret = lt8711uxe2_parse_dt(pdata);
if (ret) {
dev_err(pdata->dev, "failed to parse device tree\n");
goto err_dt_parse;
}
ret = lt8711uxe2_gpio_configure(pdata, true);
if (ret) {
dev_err(pdata->dev, "failed to configure GPIOs\n");
goto error;
}
mutex_init(&pdata->mutex);
if (gpio_is_valid(pdata->cc_finished_gpio))
lt8711uxe2_reset(pdata, false);
else
lt8711uxe2_reset(pdata, true);
ret = lt8711uxe2_read_firmware_version(pdata);
if (ret)
dev_warn(pdata->dev,
"failed to read fw version, not checking fw up to date\n");
dev_info(pdata->dev, "chip firmware version: %#06x\n",
pdata->chip_fw_version);
dev_info(pdata->dev, "image firmware version: %#06x\n",
pdata->image_fw_version);
/* determine if firmware upgrade is needed, only accept exact version */
if (pdata->image_fw_version > pdata->chip_fw_version) {
dev_info(pdata->dev, "Upgrading fw %#06x => %#06x\n",
pdata->chip_fw_version,
pdata->image_fw_version);
ret = request_firmware_nowait(THIS_MODULE, true,
"lt8711uxe2_fw.bin", &client->dev,
GFP_KERNEL, pdata,
lt8711uxe2_fw_cb_main);
if (ret) {
dev_err(pdata->dev,
"Failed to invoke firmware loader: %d\n", ret);
}
} else
dev_info(pdata->dev, "firmware up-to-date\n");
(void)sysfs_create_group(&client->dev.kobj, &lt8711uxe2_attr_group);
if (of_property_read_bool(pdata->dev->of_node, "with-redriver"))
pdata->with_redriver = true;
if (of_property_read_bool(pdata->dev->of_node, "with-hdmi-switch")) {
pdata->with_hdmi_switch = true;
}
/* Allocate extcon device */
if (pdata->with_redriver)
pdata->edev = devm_extcon_dev_allocate(pdata->dev,
lt8711uxe2_extcon_cable_with_redriver);
else
pdata->edev = devm_extcon_dev_allocate(pdata->dev,
lt8711uxe2_extcon_cable);
if (IS_ERR(pdata->edev)) {
dev_err(pdata->dev, "failed to allocate memory for extcon\n");
ret = -ENOMEM;
goto remove_group;
}
/* Register extcon device */
ret = devm_extcon_dev_register(pdata->dev, pdata->edev);
if (ret) {
dev_err(pdata->dev, "failed to register extcon device\n");
goto remove_group;
}
if (of_property_read_bool(pdata->dev->of_node, "usb-ss-support"))
pdata->usb_ss_support = true;
extcon_set_property_capability(pdata->edev, EXTCON_USB,
EXTCON_PROP_USB_VBUS);
extcon_set_property_capability(pdata->edev, EXTCON_USB_HOST,
EXTCON_PROP_USB_VBUS);
extcon_set_property_capability(pdata->edev, EXTCON_USB,
EXTCON_PROP_USB_TYPEC_POLARITY);
extcon_set_property_capability(pdata->edev, EXTCON_USB_HOST,
EXTCON_PROP_USB_TYPEC_POLARITY);
if (pdata->usb_ss_support) {
/* Support reporting polarity and speed via properties */
extcon_set_property_capability(pdata->edev, EXTCON_USB,
EXTCON_PROP_USB_SS);
extcon_set_property_capability(pdata->edev, EXTCON_USB_HOST,
EXTCON_PROP_USB_SS);
}
if (pdata->with_redriver) {
extcon_set_property_capability(pdata->edev, EXTCON_DISP_DP,
EXTCON_PROP_USB_TYPEC_POLARITY);
extcon_set_property_capability(pdata->edev, EXTCON_DISP_DP,
EXTCON_PROP_USB_SS);
}
lt8711uxe2_check_state(pdata);
lt8711uxe2_read_alt_mode(pdata);
ret = lt8711uxe2_read_alt_mode(pdata);
if (!ret) {
lt8711uxe2_set_hdmi_switch_state(pdata,
!!(pdata->alt_mode & BIT(0)));
}
/* Make sure LT8711UXE2 initialized, then enable irq. */
pdata->irq = gpio_to_irq(pdata->irq_gpio);
ret = devm_request_threaded_irq(&client->dev, pdata->irq, NULL,
lt8711uxe2_irq_thread_handler,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
"lt8711uxe2_irq", pdata);
if (ret) {
dev_err(pdata->dev, "failed to request irq\n");
goto remove_group;
}
enable_irq_wake(pdata->irq);
if (gpio_is_valid(pdata->cc_finished_gpio)) {
pdata->cc_irq = gpio_to_irq(pdata->cc_finished_gpio);
ret = devm_request_threaded_irq(&client->dev, pdata->cc_irq, NULL,
lt8711uxe2_cc_irq_thread_handler,
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
"lt8711uxe2_cc_irq", pdata);
if (ret) {
pr_err("failed to request cc_irq\n");
goto remove_group;
}
enable_irq_wake(pdata->cc_finished_gpio);
}
return 0;
remove_group:
sysfs_remove_group(&client->dev.kobj, &lt8711uxe2_attr_group);
error:
ret = lt8711uxe2_gpio_configure(pdata, false);
err_dt_parse:
return ret;
}
static int lt8711uxe2_remove(struct i2c_client *client)
{
struct lt8711uxe2 *pdata = i2c_get_clientdata(client);
sysfs_remove_group(&client->dev.kobj, &lt8711uxe2_attr_group);
lt8711uxe2_gpio_configure(pdata, false);
return 0;
}
static int lt8711uxe2_pm_suspend(struct device *dev)
{
return 0;
}
static int lt8711uxe2_pm_resume(struct device *dev)
{
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
struct lt8711uxe2 *pdata = NULL;
if (!client)
return 0;
pdata = i2c_get_clientdata(client);
if (!pdata)
return 0;
lt8711uxe2_check_state(pdata);
return 0;
}
static const struct dev_pm_ops lt8711uxe2_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(lt8711uxe2_pm_suspend, lt8711uxe2_pm_resume)
};
static struct i2c_device_id lt8711uxe2_id[] = {
{"lt,lt8711uxe2", 0 },
{}
};
static const struct of_device_id lt8711uxe2_match_table[] = {
{ .compatible = "lt,lt8711uxe2" },
{}
};
MODULE_DEVICE_TABLE(of, lt8711uxe2_match_table);
static struct i2c_driver lt8711uxe2_driver = {
.driver = {
.name = "lt8711uxe2",
.of_match_table = lt8711uxe2_match_table,
.pm = &lt8711uxe2_pm_ops,
},
.probe = lt8711uxe2_probe,
.remove = lt8711uxe2_remove,
.id_table = lt8711uxe2_id,
};
module_i2c_driver(lt8711uxe2_driver);
MODULE_LICENSE("GPL");