1511 lines
40 KiB
C
1511 lines
40 KiB
C
/*
|
|
* extcon-fsa9285.c - FSA9285 extcon driver
|
|
*
|
|
* Copyright (C) 2013 Intel Corporation
|
|
* Ramakrishna Pallala <ramakrishna.pallala@intel.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/err.h>
|
|
#include <linux/device.h>
|
|
#include <linux/printk.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/serial_core.h>
|
|
#include <linux/lnw_gpio.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/usb/phy.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/extcon.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/acpi.h>
|
|
#include <linux/acpi_gpio.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/extcon/extcon-fsa9285.h>
|
|
#include <linux/wakelock.h>
|
|
#include <linux/proc_fs.h>
|
|
/* FSA9285 I2C registers */
|
|
#define FSA9285_REG_DEVID 0x01
|
|
#define FSA9285_REG_CTRL 0x02
|
|
#define FSA9285_REG_INTR 0x03
|
|
#define FSA9285_REG_INTR_MASK 0x05
|
|
#define FSA9285_REG_OHM_CODE 0x07
|
|
#define FSA9285_REG_TIMING 0x08
|
|
#define FSA9285_REG_STATUS 0x09
|
|
#define FSA9285_REG_DEVTYPE 0x0A
|
|
#define FSA9285_REG_DACSAR 0x0B
|
|
#define FSA9285_REG_MAN_SW 0x13
|
|
#define FSA9285_REG_MAN_CHGCTRL 0x14
|
|
|
|
/* device ID value */
|
|
#define DEVID_VALUE 0x10
|
|
|
|
/* Control */
|
|
#define CTRL_EN_DCD_TOUT (1 << 7)
|
|
#define CTRL_EM_RESETB (1 << 6)
|
|
#define CTRL_EM_ID_DIS (1 << 5)
|
|
#define CTRL_EM_MAN_SW (1 << 4)
|
|
#define CTRL_INT_MASK (1 << 0)
|
|
|
|
/* Interrupts */
|
|
#define INTR_OCP_CHANGE (1 << 6)
|
|
#define INTR_OVP_CHANGE (1 << 5)
|
|
#define INTR_MIC_OVP (1 << 4)
|
|
#define INTR_OHM_CHANGE (1 << 3)
|
|
#define INTR_VBUS_CHANGE (1 << 2)
|
|
#define INTR_BC12_DONE (1 << 1)
|
|
|
|
/* resistance codes */
|
|
#define OHM_CODE_USB_SLAVE 0x16
|
|
#define OHM_CODE_UART 0x0C
|
|
#define OHM_CODE_USB_ACA 0x0A
|
|
|
|
/* Timing */
|
|
#define TIMING_500MS 0x6
|
|
|
|
/* Status */
|
|
#define STATUS_ID_SHORT (1 << 7)
|
|
#define STATUS_OCP (1 << 6)
|
|
#define STATUS_OVP (1 << 5)
|
|
#define STATUS_MIC_OVP (1 << 4)
|
|
#define STATUS_ID_NO_FLOAT (1 << 3)
|
|
#define STATUS_VBUS_VALID (1 << 2)
|
|
#define STATUS_DCD (1 << 0)
|
|
|
|
/* Device Type */
|
|
#define DEVTYPE_DOCK (1 << 5)
|
|
#define DEVTYPE_DCP (1 << 2)
|
|
#define DEVTYPE_CDP (1 << 1)
|
|
#define DEVTYPE_SDP (1 << 0)
|
|
#define DEVTYPE_HCP (1 << 3)
|
|
/*
|
|
* Manual Switch
|
|
* D- [7:5] / D+ [4:2]
|
|
* 000: Open all / 001: HOST USB1 / 010: AUDIO / 011: HOST USB2 / 100: MIC
|
|
* VBUS_IN SW[1:0]
|
|
* 00: Open all/ 01: N/A / 10: VBUS_IN to MIC / 11: VBUS_IN to VBUS_OUT
|
|
*/
|
|
#define MAN_SW_DPDM_MIC ((4 << 5) | (4 << 2))
|
|
#define MAN_SW_DPDM_HOST2 ((3 << 5) | (3 << 2))
|
|
/* same code can be used for AUDIO/UART */
|
|
#define MAN_SW_DPDM_UART ((2 << 5) | (2 << 2))
|
|
#define MAN_SW_DPDM_HOST1 ((1 << 5) | (1 << 2))
|
|
#define MAN_SW_DPDP_AUTO ((0 << 5) | (0 << 2))
|
|
#define MAN_SW_VBUSIN_MASK (3 << 0)
|
|
#define MAN_SW_VBUSIN_VOUT (3 << 0)
|
|
#define MAN_SW_VBUSIN_MIC (2 << 0)
|
|
#define MAN_SW_VBUSIN_AUTO (0 << 0)
|
|
|
|
/* Manual Charge Control */
|
|
#define CHGCTRL_ASSERT_CHG_DETB (1 << 4)
|
|
#define CHGCTRL_MIC_OVP_EN (1 << 3)
|
|
#define CHGCTRL_ASSERT_DP (1 << 2)
|
|
|
|
#define FSA_CHARGE_CUR_DCP 2000
|
|
#define FSA_CHARGE_CUR_ACA 2000
|
|
#define FSA_CHARGE_CUR_CDP 1500
|
|
#define FSA_CHARGE_CUR_SDP 500
|
|
|
|
#define FSA9285_EXTCON_SDP "CHARGER_USB_SDP"
|
|
#define FSA9285_EXTCON_DCP "CHARGER_USB_DCP"
|
|
#define FSA9285_EXTCON_CDP "CHARGER_USB_CDP"
|
|
#define FSA9285_EXTCON_ACA "CHARGER_USB_ACA"
|
|
#define FSA9285_EXTCON_DOCK "Dock"
|
|
#define FSA9285_EXTCON_USB_HOST "USB-Host"
|
|
|
|
#define MAX_RETRY 3
|
|
|
|
#define LC8x_SWITCH_SET_MODE (0)
|
|
#define SOC_USB_SWITCH_INT (131)
|
|
int flag =0; //liulc1
|
|
int mousetype=0; //liulc1 add
|
|
int fs_ok = 0;
|
|
int lenovo_charger_flag = 0;
|
|
int lenovo_h_charger_kthread_running =0;
|
|
//extern int console_flag;
|
|
int swmode = 0;
|
|
static struct file* lenovo_h_charger_uart_file = 0;
|
|
static int saved_loglevel=0;
|
|
static const char *fsa9285_extcon_cable[] = {
|
|
FSA9285_EXTCON_SDP,
|
|
FSA9285_EXTCON_DCP,
|
|
FSA9285_EXTCON_CDP,
|
|
FSA9285_EXTCON_ACA,
|
|
FSA9285_EXTCON_DOCK,
|
|
FSA9285_EXTCON_USB_HOST,
|
|
NULL,
|
|
};
|
|
|
|
struct fsa9285_chip {
|
|
struct i2c_client *client;
|
|
struct fsa9285_pdata *pdata;
|
|
struct usb_phy *otg;
|
|
struct extcon_dev *edev;
|
|
|
|
/* reg data */
|
|
u8 cntl;
|
|
u8 man_sw;
|
|
u8 man_chg_cntl;
|
|
|
|
bool vbus_drive;
|
|
bool a_bus_drop;
|
|
struct delayed_work fsa9285_wrkr; //liulc1
|
|
struct wake_lock wakelock;
|
|
};
|
|
|
|
static struct fsa9285_chip *chip_ptr;
|
|
extern void *fsa9285_platform_data(void);
|
|
extern unsigned int read_vbus(); //liulc1
|
|
//void lenovo_charger_log_on(void);
|
|
static int lenovo_h_charger_uart_config(int baud_level);
|
|
static void lenovo_h_charger_uart_close(void);
|
|
static int lenovo_charger_detection(struct fsa9285_chip *chip);
|
|
|
|
static int fsa9285_write_reg(struct i2c_client *client,
|
|
int reg, int value)
|
|
{
|
|
int ret;
|
|
int retry;
|
|
|
|
for (retry = 0; retry < MAX_RETRY; retry++) {
|
|
ret = i2c_smbus_write_byte_data(client, reg, value);
|
|
if (ret < 0)
|
|
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
|
|
else
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int fsa9285_read_reg(struct i2c_client *client, int reg)
|
|
{
|
|
int ret;
|
|
int retry;
|
|
|
|
for (retry = 0; retry < MAX_RETRY; retry++) {
|
|
ret = i2c_smbus_read_byte_data(client, reg);
|
|
if (ret < 0)
|
|
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
|
|
else
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void fsa9285_vbus_cntl_state(struct usb_phy *phy)
|
|
{
|
|
struct fsa9285_chip *chip = chip_ptr;
|
|
int ret = 0;
|
|
|
|
if (!chip)
|
|
return;
|
|
|
|
if (phy->vbus_state == VBUS_DISABLED) {
|
|
dev_info(&chip->client->dev,
|
|
"a_bus_drop event(true)\n");
|
|
chip->a_bus_drop = true;
|
|
if (chip->vbus_drive)
|
|
ret = chip->pdata->disable_vbus();
|
|
} else {
|
|
dev_info(&chip->client->dev,
|
|
"a_bus_drop event(false)\n");
|
|
chip->a_bus_drop = false;
|
|
if (chip->vbus_drive)
|
|
ret = chip->pdata->enable_vbus();
|
|
}
|
|
|
|
if (ret < 0)
|
|
dev_warn(&chip->client->dev,
|
|
"pmic vbus control failed\n");
|
|
}
|
|
|
|
static void usb_otg(struct fsa9285_chip *chip)
|
|
{
|
|
struct i2c_client *client = chip->client;
|
|
int otg_vbus_mask = 1 ; //liulc1
|
|
atomic_notifier_call_chain(&chip->otg->notifier,USB_EVENT_DRIVE_VBUS, &otg_vbus_mask); //liulc1
|
|
mdelay(10); //liulc1
|
|
|
|
printk("%s, USB-OTG, DP/DN switch to HOST\n", __func__);
|
|
fsa9285_write_reg(client, 0x2, 0xEC);
|
|
}
|
|
//liulc1 add
|
|
static void usb_plug_out_otg_vbus(struct fsa9285_chip *chip)
|
|
{
|
|
struct i2c_client *client = chip->client;
|
|
int otg_vbus_mask = 0 ; //liulc1
|
|
atomic_notifier_call_chain(&chip->otg->notifier,USB_EVENT_DRIVE_VBUS, &otg_vbus_mask); //liulc1
|
|
mdelay(10); //liulc1
|
|
|
|
fsa9285_write_reg(client, 0x2, 0xF8);
|
|
fsa9285_write_reg(client, 0x5, 0x7F);
|
|
fsa9285_write_reg(client, 0x5, 0x00);
|
|
|
|
fsa9285_write_reg(client, 0x6, 0x6C); //liulc1 add
|
|
|
|
}
|
|
|
|
//liulc1 end
|
|
static void get_id(struct fsa9285_chip *chip)
|
|
{
|
|
struct i2c_client *client = chip->client;
|
|
int id;
|
|
|
|
printk("%s\n", __func__);
|
|
|
|
// ADC always ON
|
|
fsa9285_write_reg(client, 0x7, 0x44); //liulc1 add
|
|
// ID read
|
|
id = fsa9285_read_reg(client, 0x3);
|
|
// ADC normal
|
|
fsa9285_write_reg(client, 0x7, 0x40); //liulc1 add
|
|
|
|
if(id == 0x10)
|
|
{
|
|
usb_otg(chip);
|
|
flag=1;
|
|
}
|
|
|
|
}
|
|
|
|
static void charger_detect_retry(struct fsa9285_chip *chip)
|
|
{
|
|
struct i2c_client *client = chip->client;
|
|
|
|
printk("%s\n", __func__);
|
|
|
|
// interrupt mask
|
|
fsa9285_write_reg(client, 0x6, 0x7F);
|
|
// charger re-detection OFF
|
|
fsa9285_write_reg(client, 0x8, 0x0);
|
|
// OVP, VBUS, CHG, ID mask off
|
|
fsa9285_write_reg(client, 0x6, 0x6C); //liulc1 add
|
|
//charger re-detection ON
|
|
fsa9285_write_reg(client, 0x8, 0x1);
|
|
}
|
|
|
|
static void usb_plug_out(struct fsa9285_chip *chip)
|
|
{
|
|
struct i2c_client *client = chip->client;
|
|
|
|
printk("%s\n", __func__);
|
|
|
|
if(swmode==0)
|
|
{
|
|
fsa9285_write_reg(client, 0x2, 0xF8);
|
|
fsa9285_write_reg(client, 0x5, 0x7F);
|
|
fsa9285_write_reg(client, 0x5, 0x00);
|
|
|
|
fsa9285_write_reg(client, 0x6, 0x6C); //liulc1 add
|
|
if (lenovo_charger_flag == 1)
|
|
{
|
|
lnw_gpio_set_alt(57, 1);
|
|
lnw_gpio_set_alt(61, 1);
|
|
//lenovo_h_charger_uart_config(115200);
|
|
//lenovo_charger_log_on();
|
|
//console_flag = 0;
|
|
//lenovo_h_charger_uart_close();
|
|
lenovo_charger_flag = 0;
|
|
//cancel_delayed_work_sync(&chip->fsa9285_wrkr); //liulc1
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
static int lenovo_h_charger_uart_config(int baud_level)
|
|
{
|
|
struct termios settings;
|
|
mm_segment_t oldfs;
|
|
printk("%s\n", __func__);
|
|
struct file* temp=0;
|
|
lenovo_h_charger_uart_file = filp_open("/dev/ttyS0", O_RDWR|O_NDELAY, 0664); //open file
|
|
if(lenovo_h_charger_uart_file <= 0)
|
|
{
|
|
printk("%s Open /dev/console failed\n", __func__);
|
|
return -1;
|
|
}
|
|
printk("%s Open /dev/ttyS0 sucessed\n", __func__);
|
|
oldfs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
|
|
printk("Begin to set baudrate.\n");
|
|
//set baud test
|
|
|
|
lenovo_h_charger_uart_file->f_op->unlocked_ioctl(lenovo_h_charger_uart_file, TCGETS, (unsigned long)&settings);
|
|
settings.c_cflag &= ~CBAUD;
|
|
switch(baud_level)
|
|
{
|
|
case 300:
|
|
settings.c_cflag |= B300;
|
|
break;
|
|
case 600:
|
|
settings.c_cflag |= B600;
|
|
break;
|
|
case 0:
|
|
case 921600:
|
|
settings.c_cflag |= B921600;
|
|
break;
|
|
default:
|
|
settings.c_cflag |= B115200;
|
|
break;
|
|
}
|
|
|
|
// settings.c_cflag &= ~PARENB;
|
|
// settings.c_cflag &= ~CSTOPB;
|
|
// settings.c_cflag &= CSIZE;
|
|
// settings.c_cflag |= CS8;
|
|
settings.c_lflag &= ~ECHO;
|
|
settings.c_lflag &= ~ICANON;
|
|
// settings.c_lflag &= ~ECHOE;
|
|
// settings.c_lflag &= ~ISIG;
|
|
// settings.c_oflag &= ~OPOST;
|
|
lenovo_h_charger_uart_file->f_op->unlocked_ioctl(lenovo_h_charger_uart_file, TCSETS, (unsigned long)&settings);
|
|
set_fs(oldfs);
|
|
return 1;
|
|
|
|
}
|
|
|
|
static int lenovo_h_charger_uart_write(unsigned char *data, int len)
|
|
{
|
|
int num;
|
|
mm_segment_t oldfs;
|
|
{
|
|
oldfs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
//write to TX
|
|
num = lenovo_h_charger_uart_file->f_op->write(lenovo_h_charger_uart_file, data, len, &lenovo_h_charger_uart_file->f_pos);
|
|
set_fs(oldfs);
|
|
if(num != len)
|
|
{
|
|
printk("write_byte_test error: len =%d, num = %d", len,num);
|
|
}
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
static int lenovo_h_charger_uart_read(unsigned char *data)
|
|
{
|
|
int len;
|
|
mm_segment_t oldfs;
|
|
struct termios settings;
|
|
{
|
|
oldfs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
len = lenovo_h_charger_uart_file->f_op->read(lenovo_h_charger_uart_file, data, 1024, &lenovo_h_charger_uart_file->f_pos);
|
|
set_fs(oldfs);
|
|
}
|
|
return len;
|
|
}
|
|
#if 0
|
|
void lenovo_charger_log_on(void)
|
|
{
|
|
console_flag = 0;
|
|
return;
|
|
}
|
|
void lenovo_charger_log_off(void)
|
|
{
|
|
console_flag = 1;
|
|
return;
|
|
}
|
|
|
|
#endif
|
|
|
|
static void lenovo_h_charger_uart_close()
|
|
{
|
|
if(lenovo_h_charger_uart_file >0)
|
|
{
|
|
filp_close(lenovo_h_charger_uart_file, NULL);
|
|
lenovo_h_charger_uart_file = 0 ;
|
|
}
|
|
}
|
|
static int lenovo_h_charger_switch(int val,struct fsa9285_chip *chip)
|
|
{
|
|
struct i2c_client *client = chip->client;
|
|
|
|
printk("%s\n", __func__);
|
|
if (val ==0)
|
|
{
|
|
fsa9285_write_reg(client,0x0,0x1);
|
|
fsa9285_write_reg(client, 0x2, 0xf8);
|
|
}
|
|
else
|
|
{
|
|
fsa9285_write_reg(client,0x0,0x1);
|
|
fsa9285_write_reg(client, 0x2, 0xC8);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int lenovo_h_charger_config()
|
|
{
|
|
printk("%s\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static int lenovo_h_charger_kthread(void *x)
|
|
{
|
|
static char *cable;
|
|
static struct power_supply_cable_props cable_props;
|
|
lenovo_h_charger_kthread_running =1;
|
|
while (fs_ok == 0)
|
|
{
|
|
msleep(100);
|
|
printk("waiting for the user space file system......\n");
|
|
}
|
|
{
|
|
if(lenovo_charger_detection(chip_ptr))
|
|
{
|
|
/*
|
|
cable = FSA9285_EXTCON_DCP;
|
|
cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_CONNECT;
|
|
cable_props.chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_DCP;
|
|
cable_props.ma = FSA_CHARGE_CUR_DCP;
|
|
if (!wake_lock_active(&chip_ptr->wakelock))
|
|
wake_lock(&chip_ptr->wakelock);
|
|
atomic_notifier_call_chain(&power_supply_notifier,
|
|
POWER_SUPPLY_CABLE_EVENT, &cable_props);
|
|
*/
|
|
}
|
|
}
|
|
|
|
|
|
lenovo_h_charger_kthread_running =0;
|
|
return 1;
|
|
}
|
|
|
|
static int lenovo_charger_detection(struct fsa9285_chip *chip)
|
|
{
|
|
int cnt = 0;
|
|
int i;
|
|
struct i2c_client *client = chip->client;
|
|
int devtype =0;
|
|
printk("%s\n", __func__);
|
|
|
|
//Mute the debug infomations
|
|
//lenovo_charger_log_off();
|
|
//msleep(100);
|
|
lenovo_h_charger_switch(1,chip); //switch the USB path to MIC-ON
|
|
if(lenovo_h_charger_uart_config(600)==-1)
|
|
return 1;
|
|
mdelay(10);
|
|
int len = 0;
|
|
unsigned char buf[1024];
|
|
int ret = 0;
|
|
printk("Before ww_debug tx %d cnt=%d\n", len, i);
|
|
len = lenovo_h_charger_uart_write("SC", 2);
|
|
mdelay(50);
|
|
lenovo_h_charger_uart_close();
|
|
lnw_gpio_set_alt(57, 0);
|
|
lnw_gpio_set_alt(61, 0);
|
|
gpio_direction_output(57, 1);
|
|
gpio_direction_output(61, 1);
|
|
lenovo_charger_flag =1;
|
|
return 1;
|
|
|
|
}
|
|
|
|
static int normal_charger_detection(struct fsa9285_chip *chip)
|
|
{
|
|
struct i2c_client *client = chip->client;
|
|
int charger_type;
|
|
int cdp_det;
|
|
int devtype;
|
|
charger_type = fsa9285_read_reg(client, 0x9);
|
|
mousetype=charger_type;
|
|
printk("%s, charger_type=0x%x\n", __func__, charger_type);
|
|
if(charger_type==0x0) return 0;
|
|
if(charger_type == 0x4)
|
|
{
|
|
cdp_det = fsa9285_read_reg(client, 0x8);
|
|
cdp_det = (cdp_det & 0xFD) | 0x2; // read back -> clear bit[1] -> set bit[1] to 1
|
|
printk("%s, [W]cdp_det=0x%x\n", __func__, cdp_det);
|
|
fsa9285_write_reg(client, 0x8, cdp_det);
|
|
|
|
mdelay(100);
|
|
cdp_det = fsa9285_read_reg(client, 0x8);
|
|
|
|
printk("%s, [R]cdp_det=0x%x\n", __func__, cdp_det);
|
|
|
|
if( (cdp_det & 0x04) == 0x04)
|
|
{
|
|
printk("%s, CDP detection\n", __func__);
|
|
devtype = DEVTYPE_CDP;
|
|
}
|
|
else
|
|
{
|
|
printk("%s, SDP detection\n", __func__);
|
|
devtype = DEVTYPE_SDP;
|
|
}
|
|
// CDP detection OFF
|
|
cdp_det = fsa9285_read_reg(client, 0x8);
|
|
cdp_det = (cdp_det & 0xFD); // 0b1111_1101
|
|
printk("%s, [W]cdp_det=0x%x\n", __func__, cdp_det);
|
|
fsa9285_write_reg(client, 0x8, cdp_det);
|
|
|
|
//Switch USB1-ON
|
|
printk("%s DP/DM switch to USB-OTG-DEVICE\n", __func__);
|
|
if(swmode!=1)
|
|
fsa9285_write_reg(client, 0x2, 0xFC);
|
|
}
|
|
|
|
if (charger_type == 0x1)
|
|
{
|
|
printk("%s, DCP detection\n", __func__);
|
|
devtype = DEVTYPE_DCP;
|
|
//if (lenovo_charger_detection(chip)!=0)
|
|
// devtype = DEVTYPE_DCP;
|
|
//if (lenovo_h_charger_kthread_running ==0)
|
|
// kthread_run(lenovo_h_charger_kthread, NULL, "lenovo_h_charger_kthread");
|
|
|
|
schedule_delayed_work(&chip->fsa9285_wrkr,HZ*0); //liulc1
|
|
}
|
|
|
|
return devtype;
|
|
}
|
|
|
|
static int charger_detect(struct fsa9285_chip *chip)
|
|
{
|
|
struct i2c_client *client = chip->client;
|
|
int charger_type;
|
|
|
|
charger_type = fsa9285_read_reg(client, 0x9);
|
|
|
|
printk("%s, charger_type=0x%x\n", __func__, charger_type);
|
|
charger_type = normal_charger_detection(chip);
|
|
|
|
return charger_type;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ovp_accessory(struct fsa9285_chip *chip)
|
|
{
|
|
|
|
}
|
|
|
|
void dcp_plug_out(struct fsa9285_chip *chip)
|
|
{
|
|
static struct power_supply_cable_props cable_props;
|
|
|
|
|
|
cable_props.ma = 0;
|
|
cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_DISCONNECT;
|
|
cable_props.chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_DCP;
|
|
|
|
usb_plug_out(chip);
|
|
atomic_notifier_call_chain(&power_supply_notifier,POWER_SUPPLY_CABLE_EVENT, &cable_props);
|
|
|
|
}
|
|
|
|
static int fsa9285_detect_dev(struct fsa9285_chip *chip)
|
|
{
|
|
struct i2c_client *client = chip->client;
|
|
static bool notify_otg, notify_charger;
|
|
static char *cable;
|
|
static struct power_supply_cable_props cable_props;
|
|
int stat, devtype, ohm_code, cntl, intmask, ret;
|
|
u8 w_man_sw, w_man_chg_cntl;
|
|
bool discon_evt = false, drive_vbus = false;
|
|
int vbus_mask = 0;
|
|
int usb_switch = 1;
|
|
unsigned int intr_status;
|
|
unsigned int switch_status;
|
|
|
|
// interrupt status read
|
|
intr_status = fsa9285_read_reg(client, 0x4);
|
|
|
|
// interrupt clear
|
|
//fsa9285_write_reg(client, 0x5, intr_status);
|
|
fsa9285_write_reg(client, 0x5, 0x7f); //liulc1 modify
|
|
fsa9285_write_reg(client, 0x5, 0);
|
|
|
|
//switch status read
|
|
switch_status = fsa9285_read_reg(client, 0x1);
|
|
|
|
printk("%s, intr_status=0x%x, switch_status=0x%x \n", __func__,
|
|
intr_status,
|
|
switch_status);
|
|
devtype = 0;
|
|
|
|
|
|
if(switch_status == 0xF8)
|
|
{
|
|
/* disconnect event */
|
|
discon_evt = true;
|
|
/* usb switch off per nothing attached */
|
|
usb_switch = 0;
|
|
cable_props.ma = 0;
|
|
cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_DISCONNECT;
|
|
if(flag==1)
|
|
{
|
|
flag=0;
|
|
usb_plug_out_otg_vbus(chip);
|
|
}
|
|
else
|
|
{
|
|
usb_plug_out(chip);
|
|
}
|
|
}
|
|
else if(switch_status == 0xF9)
|
|
{
|
|
printk("OVP accessory\n", __func__);
|
|
ovp_accessory(chip);
|
|
}
|
|
else if(switch_status == 0x80)
|
|
{
|
|
//Note: Reserve USB_ID pin to PHY, OTG handle device enumerate, LC8x handle USB DP/DM switch
|
|
usb_otg(chip);
|
|
flag=1;
|
|
}
|
|
else
|
|
{
|
|
printk("%s, charge=0x%x, 0x%x\n", __func__, (switch_status>>3), (switch_status &0x4) );
|
|
if( (switch_status >> 3) == 0x1F)
|
|
{
|
|
if( (switch_status & 0x05) == 0x4) //liulc1 add
|
|
{
|
|
devtype = charger_detect(chip);
|
|
if(mousetype==0x06)
|
|
{
|
|
mousetype==0;
|
|
flag=0;
|
|
usb_plug_out_otg_vbus(chip);
|
|
return;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (devtype & DEVTYPE_SDP) {
|
|
dev_info(&chip->client->dev,
|
|
"SDP cable connecetd\n");
|
|
/* select Host2 */
|
|
notify_otg = true;
|
|
vbus_mask = 1;
|
|
notify_charger = true;
|
|
cable = FSA9285_EXTCON_SDP;
|
|
cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_CONNECT;
|
|
cable_props.chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_SDP;
|
|
cable_props.ma = FSA_CHARGE_CUR_SDP;
|
|
} else if (devtype & DEVTYPE_CDP) {
|
|
dev_info(&chip->client->dev,
|
|
"CDP cable connecetd\n");
|
|
/* select Host2 */
|
|
notify_otg = true;
|
|
vbus_mask = 1;
|
|
notify_charger = true;
|
|
cable = FSA9285_EXTCON_CDP;
|
|
cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_CONNECT;
|
|
cable_props.chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_CDP;
|
|
cable_props.ma = FSA_CHARGE_CUR_CDP;
|
|
} else if (devtype & DEVTYPE_DCP) {
|
|
dev_info(&chip->client->dev,
|
|
"DCP cable connecetd\n");
|
|
notify_charger = true;
|
|
cable = FSA9285_EXTCON_DCP;
|
|
cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_CONNECT;
|
|
cable_props.chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_DCP;
|
|
cable_props.ma = FSA_CHARGE_CUR_DCP;
|
|
if (!wake_lock_active(&chip->wakelock))
|
|
wake_lock(&chip->wakelock);
|
|
} else if (devtype & DEVTYPE_HCP){
|
|
dev_info(&chip->client->dev,
|
|
"HCP cable connecetd\n");
|
|
notify_charger = true;
|
|
cable = FSA9285_EXTCON_DCP;
|
|
cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_CONNECT;
|
|
cable_props.chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_HCP;
|
|
cable_props.ma = FSA_CHARGE_CUR_DCP;
|
|
if (!wake_lock_active(&chip->wakelock))
|
|
wake_lock(&chip->wakelock);
|
|
|
|
}
|
|
|
|
printk("%s, discon_evt=%d, notify_otg=%d, notify_charger=%d, vbus_mask=%d\n",
|
|
__func__,
|
|
discon_evt,
|
|
notify_otg,
|
|
notify_charger,
|
|
vbus_mask);
|
|
|
|
if (discon_evt) {
|
|
if (notify_otg) {
|
|
atomic_notifier_call_chain(&chip->otg->notifier,
|
|
USB_EVENT_VBUS, &vbus_mask);
|
|
notify_otg = false;
|
|
}
|
|
if (notify_charger) {
|
|
atomic_notifier_call_chain(&power_supply_notifier,
|
|
POWER_SUPPLY_CABLE_EVENT, &cable_props);
|
|
notify_charger = false;
|
|
cable = NULL;
|
|
}
|
|
if (wake_lock_active(&chip->wakelock))
|
|
wake_unlock(&chip->wakelock);
|
|
} else {
|
|
if (notify_otg)
|
|
atomic_notifier_call_chain(&chip->otg->notifier,
|
|
USB_EVENT_VBUS, &vbus_mask);
|
|
if (notify_charger) {
|
|
atomic_notifier_call_chain(&power_supply_notifier,
|
|
POWER_SUPPLY_CABLE_EVENT, &cable_props);
|
|
}
|
|
}
|
|
|
|
|
|
#if 0
|
|
/* read status registers */
|
|
ret = fsa9285_read_reg(client, FSA9285_REG_CTRL);
|
|
if (ret < 0)
|
|
goto dev_det_i2c_failed;
|
|
else
|
|
cntl = ret;
|
|
|
|
ret = fsa9285_read_reg(client, FSA9285_REG_DEVTYPE);
|
|
if (ret < 0)
|
|
goto dev_det_i2c_failed;
|
|
else
|
|
devtype = ret;
|
|
|
|
ret = fsa9285_read_reg(client, FSA9285_REG_STATUS);
|
|
if (ret < 0)
|
|
goto dev_det_i2c_failed;
|
|
else
|
|
stat = ret;
|
|
|
|
ret = fsa9285_read_reg(client, FSA9285_REG_OHM_CODE);
|
|
if (ret < 0)
|
|
goto dev_det_i2c_failed;
|
|
else
|
|
ohm_code = ret;
|
|
|
|
dev_info(&client->dev, "devtype:%x, Stat:%x, ohm:%x cntl:%x\n",
|
|
devtype, stat, ohm_code, cntl);
|
|
|
|
/* set default register setting */
|
|
w_man_sw = (chip->man_sw & 0x3) | MAN_SW_DPDM_HOST1;
|
|
w_man_chg_cntl = chip->man_chg_cntl & ~CHGCTRL_ASSERT_CHG_DETB;
|
|
|
|
if (stat & STATUS_ID_SHORT) {
|
|
if (ohm_code == OHM_CODE_USB_SLAVE) {
|
|
dev_info(&chip->client->dev,
|
|
"USB slave device connecetd\n");
|
|
drive_vbus = true;
|
|
}
|
|
} else if ((stat & STATUS_ID_NO_FLOAT) && (stat & STATUS_VBUS_VALID)) {
|
|
if (ohm_code == OHM_CODE_UART) {
|
|
dev_info(&chip->client->dev,
|
|
"UART device connecetd\n");
|
|
/* select UART */
|
|
w_man_sw = (chip->man_sw & 0x3) | MAN_SW_DPDM_UART;
|
|
} else if (ohm_code == OHM_CODE_USB_ACA) {
|
|
dev_info(&chip->client->dev,
|
|
"ACA device connecetd\n");
|
|
notify_charger = true;
|
|
cable = FSA9285_EXTCON_ACA;
|
|
cable_props.chrg_evt =
|
|
POWER_SUPPLY_CHARGER_EVENT_CONNECT;
|
|
cable_props.chrg_type =
|
|
POWER_SUPPLY_CHARGER_TYPE_USB_ACA;
|
|
cable_props.ma = FSA_CHARGE_CUR_ACA;
|
|
if (!wake_lock_active(&chip->wakelock))
|
|
wake_lock(&chip->wakelock);
|
|
} else {
|
|
/* unknown device */
|
|
dev_warn(&chip->client->dev, "unknown ID detceted\n");
|
|
}
|
|
} else if (devtype & DEVTYPE_SDP) {
|
|
dev_info(&chip->client->dev,
|
|
"SDP cable connecetd\n");
|
|
/* select Host2 */
|
|
w_man_sw = (chip->man_sw & 0x3) | MAN_SW_DPDM_HOST2;
|
|
w_man_chg_cntl = chip->man_chg_cntl | CHGCTRL_ASSERT_CHG_DETB;
|
|
notify_otg = true;
|
|
vbus_mask = 1;
|
|
} else if (devtype & DEVTYPE_CDP) {
|
|
dev_info(&chip->client->dev,
|
|
"CDP cable connecetd\n");
|
|
/* select Host2 */
|
|
w_man_sw = (chip->man_sw & 0x3) | MAN_SW_DPDM_HOST2;
|
|
notify_otg = true;
|
|
vbus_mask = 1;
|
|
notify_charger = true;
|
|
cable = FSA9285_EXTCON_CDP;
|
|
cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_CONNECT;
|
|
cable_props.chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_CDP;
|
|
cable_props.ma = FSA_CHARGE_CUR_CDP;
|
|
} else if (devtype & DEVTYPE_DCP) {
|
|
dev_info(&chip->client->dev,
|
|
"DCP cable connecetd\n");
|
|
notify_charger = true;
|
|
cable = FSA9285_EXTCON_DCP;
|
|
cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_CONNECT;
|
|
cable_props.chrg_type = POWER_SUPPLY_CHARGER_TYPE_USB_DCP;
|
|
cable_props.ma = FSA_CHARGE_CUR_DCP;
|
|
if (!wake_lock_active(&chip->wakelock))
|
|
wake_lock(&chip->wakelock);
|
|
} else if (devtype & DEVTYPE_DOCK) {
|
|
dev_info(&chip->client->dev,
|
|
"Dock connecetd\n");
|
|
notify_charger = true;
|
|
cable = FSA9285_EXTCON_DOCK;
|
|
cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_CONNECT;
|
|
cable_props.chrg_type = POWER_SUPPLY_CHARGER_TYPE_ACA_DOCK;
|
|
cable_props.ma = FSA_CHARGE_CUR_ACA;
|
|
if (!wake_lock_active(&chip->wakelock))
|
|
wake_lock(&chip->wakelock);
|
|
} else {
|
|
dev_warn(&chip->client->dev,
|
|
"ID or VBUS change event\n");
|
|
if (stat & STATUS_VBUS_VALID)
|
|
chip->cntl = cntl | CTRL_EN_DCD_TOUT;
|
|
else
|
|
chip->cntl = cntl & ~CTRL_EN_DCD_TOUT;
|
|
|
|
ret = fsa9285_write_reg(client, FSA9285_REG_CTRL, chip->cntl);
|
|
if (ret < 0)
|
|
dev_warn(&chip->client->dev, "i2c write failed\n");
|
|
/* disconnect event */
|
|
discon_evt = true;
|
|
/* usb switch off per nothing attached */
|
|
usb_switch = 0;
|
|
cable_props.ma = 0;
|
|
cable_props.chrg_evt = POWER_SUPPLY_CHARGER_EVENT_DISCONNECT;
|
|
}
|
|
|
|
/* VBUS control */
|
|
if (drive_vbus && !chip->a_bus_drop) {
|
|
intmask = fsa9285_read_reg(client, FSA9285_REG_INTR_MASK);
|
|
if (intmask < 0)
|
|
goto dev_det_i2c_failed;
|
|
|
|
/* disable VBUS interrupt */
|
|
ret = fsa9285_write_reg(client, FSA9285_REG_INTR_MASK,
|
|
intmask | INTR_VBUS_CHANGE);
|
|
if (ret < 0)
|
|
goto dev_det_i2c_failed;
|
|
|
|
ret = chip->pdata->enable_vbus();
|
|
|
|
/* clear interrupt */
|
|
ret = fsa9285_read_reg(client, FSA9285_REG_INTR);
|
|
if (ret < 0)
|
|
dev_err(&chip->client->dev,
|
|
"i2c read failed:%d\n", ret);
|
|
|
|
/* enable VBUS interrupt */
|
|
ret = fsa9285_write_reg(client, FSA9285_REG_INTR_MASK,
|
|
intmask);
|
|
if (ret < 0)
|
|
goto dev_det_i2c_failed;
|
|
|
|
} else
|
|
ret = chip->pdata->disable_vbus();
|
|
if (ret < 0)
|
|
dev_warn(&chip->client->dev,
|
|
"pmic vbus control failed\n");
|
|
chip->vbus_drive = drive_vbus;
|
|
|
|
/* handle SDP case before enabling CHG_DETB */
|
|
if (w_man_chg_cntl & CHGCTRL_ASSERT_CHG_DETB)
|
|
ret = chip->pdata->sdp_pre_setup();
|
|
else if (!discon_evt)
|
|
ret = chip->pdata->sdp_post_setup();
|
|
|
|
if (ret < 0)
|
|
dev_warn(&chip->client->dev,
|
|
"sdp cable control failed\n");
|
|
|
|
if (chip->pdata->xsd_gpio != -1) {
|
|
if (usb_switch)
|
|
gpio_direction_output(chip->pdata->xsd_gpio, 0);
|
|
else
|
|
gpio_direction_output(chip->pdata->xsd_gpio, 1);
|
|
}
|
|
|
|
if (chip->pdata->mux_gpio != -1) {
|
|
if (vbus_mask)
|
|
gpio_direction_output(chip->pdata->mux_gpio, 1);
|
|
else
|
|
gpio_direction_output(chip->pdata->mux_gpio, 0);
|
|
}
|
|
|
|
/* enable manual charge detection */
|
|
ret = fsa9285_write_reg(client,
|
|
FSA9285_REG_MAN_CHGCTRL, w_man_chg_cntl);
|
|
if (ret < 0)
|
|
goto dev_det_i2c_failed;
|
|
|
|
/* select the controller */
|
|
ret = fsa9285_write_reg(client, FSA9285_REG_MAN_SW, w_man_sw);
|
|
if (ret < 0)
|
|
goto dev_det_i2c_failed;
|
|
|
|
if (discon_evt) {
|
|
if (notify_otg) {
|
|
atomic_notifier_call_chain(&chip->otg->notifier,
|
|
USB_EVENT_VBUS, &vbus_mask);
|
|
notify_otg = false;
|
|
}
|
|
if (notify_charger) {
|
|
atomic_notifier_call_chain(&power_supply_notifier,
|
|
POWER_SUPPLY_CABLE_EVENT, &cable_props);
|
|
notify_charger = false;
|
|
cable = NULL;
|
|
}
|
|
if (wake_lock_active(&chip->wakelock))
|
|
wake_unlock(&chip->wakelock);
|
|
} else {
|
|
if (notify_otg)
|
|
atomic_notifier_call_chain(&chip->otg->notifier,
|
|
USB_EVENT_VBUS, &vbus_mask);
|
|
if (notify_charger) {
|
|
atomic_notifier_call_chain(&power_supply_notifier,
|
|
POWER_SUPPLY_CABLE_EVENT, &cable_props);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
dev_det_i2c_failed:
|
|
dev_err(&chip->client->dev, "i2c read failed:%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
static irqreturn_t fsa9285_irq_handler(int irq, void *data)
|
|
{
|
|
struct fsa9285_chip *chip = data;
|
|
struct i2c_client *client = chip->client;
|
|
int ret;
|
|
int intr_status;
|
|
|
|
#if 1
|
|
pm_runtime_get_sync(&chip->client->dev);
|
|
|
|
/* clear interrupt */
|
|
//ret = fsa9285_read_reg(client, FSA9285_REG_INTR);
|
|
printk("%s\n", __func__);
|
|
// interrupt factor: 0x4H
|
|
intr_status = fsa9285_read_reg(client, 0x4);
|
|
|
|
if(intr_status == 0)
|
|
{
|
|
printk("%s, false alarm\n", __func__);
|
|
goto isr_ret;
|
|
}
|
|
else
|
|
{
|
|
printk("%s intr_status=0x%x\n", __func__, intr_status);
|
|
}
|
|
|
|
//mdelay(10);
|
|
|
|
/* device detection */
|
|
ret = fsa9285_detect_dev(chip);
|
|
|
|
if (ret < 0)
|
|
dev_err(&chip->client->dev,
|
|
"fsa9285 detecting devices failed:%d\n", ret);
|
|
isr_ret:
|
|
pm_runtime_put_sync(&chip->client->dev);
|
|
#endif
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int fsa9285_irq_init(struct fsa9285_chip *chip)
|
|
{
|
|
struct i2c_client *client = chip->client;
|
|
int ret, gpio_num, cntl, man_sw, man_chg_cntl;
|
|
struct acpi_gpio_info gpio_info;
|
|
int irq_gpio;
|
|
int irq;
|
|
int intr_status;
|
|
|
|
fsa9285_write_reg(client, 0x0, 0x01);
|
|
fsa9285_write_reg(client, 0x2, 0xF8);
|
|
fsa9285_write_reg(client, 0x10, 0x0); //liulc1 add
|
|
mdelay(100);
|
|
|
|
fsa9285_write_reg(client, 0x5, 0x7F);
|
|
fsa9285_write_reg(client, 0x5, 0x0);
|
|
fsa9285_write_reg(client, 0x6, 0x6C); //liulc1 add
|
|
|
|
// interrupt factor: 0x4H
|
|
intr_status = fsa9285_read_reg(client, 0x4);
|
|
|
|
printk("%s, clear and enable interrupt, intr_status=0x%x\n", __func__, intr_status);
|
|
|
|
irq_gpio = SOC_USB_SWITCH_INT;
|
|
irq = gpio_to_irq(irq_gpio);
|
|
client->irq = irq ; //liulc1
|
|
ret = request_threaded_irq(irq, NULL,
|
|
fsa9285_irq_handler,
|
|
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
|
"lc8260204", chip);
|
|
|
|
if (ret) {
|
|
dev_err(&client->dev, "failed to reqeust IRQ\n");
|
|
return ret;
|
|
}
|
|
|
|
enable_irq_wake(client->irq);
|
|
|
|
return 0;
|
|
|
|
irq_i2c_failed:
|
|
dev_err(&chip->client->dev, "i2c read failed:%d\n", ret);
|
|
return ret;
|
|
}
|
|
//liulc1 add for proc
|
|
|
|
|
|
static ssize_t
|
|
read_proc(struct file *file, char __user *buf,
|
|
size_t size, loff_t *ppos)
|
|
{
|
|
*ppos=1;
|
|
printk("%d\n",size);
|
|
size = 1 ;
|
|
//*buf = '1';
|
|
copy_to_user(buf,"1",1);
|
|
printk("liulc1 enable UART3 mode OK\n");
|
|
return size;
|
|
}
|
|
|
|
static ssize_t
|
|
write_proc(struct file *file, const char __user * buffer,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct fsa9285_chip *chip = PDE_DATA(file_inode(file));
|
|
struct i2c_client *client = chip->client;
|
|
|
|
if(*buffer=='1')
|
|
{
|
|
swmode = 1;
|
|
fsa9285_write_reg(client,0x0,0x1);
|
|
fsa9285_write_reg(client, 0x2, 0xC8);
|
|
printk("liulc1 enable UART3 mode\n");
|
|
}
|
|
else if(*buffer=='0')
|
|
{
|
|
swmode = 0;
|
|
fsa9285_write_reg(client, 0x2, 0xFC);
|
|
printk("liulc1 enable USB1 mode\n");
|
|
}
|
|
else if (*buffer =='3')
|
|
{
|
|
printk("FS OK\n");
|
|
fs_ok = 1;
|
|
}
|
|
|
|
|
|
|
|
return count;
|
|
}
|
|
|
|
static const struct file_operations fops = {
|
|
//.read = read_proc,
|
|
.write = write_proc,
|
|
};
|
|
|
|
|
|
|
|
//liulc1 end
|
|
|
|
|
|
#if 1
|
|
|
|
static void fsa9285_detection_worker(struct work_struct *work)
|
|
{
|
|
struct fsa9285_chip *chip =
|
|
container_of(work, struct fsa9285_chip, fsa9285_wrkr.work);
|
|
struct i2c_client *client = chip->client;
|
|
int i=0,ch;
|
|
unsigned int sw_status;
|
|
lenovo_charger_flag =1;
|
|
if(lenovo_h_charger_kthread_running==1)
|
|
return;
|
|
else
|
|
lenovo_h_charger_kthread_running=1;
|
|
|
|
if(fs_ok == 0)
|
|
{
|
|
lenovo_h_charger_kthread_running=0;
|
|
printk("waiting for the user space file system......\n");
|
|
schedule_delayed_work(&chip->fsa9285_wrkr,HZ*1);
|
|
return ;
|
|
}
|
|
|
|
|
|
if(read_vbus()<10500)
|
|
{
|
|
while(i<5)
|
|
{
|
|
i++;
|
|
lnw_gpio_set_alt(57, 1);
|
|
lnw_gpio_set_alt(61, 1);
|
|
mdelay(10);
|
|
lenovo_h_charger_switch(1,chip); //switch the USB path to MIC-ON
|
|
if(lenovo_h_charger_uart_config(600)==-1)
|
|
{
|
|
lenovo_h_charger_kthread_running=0;
|
|
return;
|
|
}
|
|
mdelay(10);
|
|
|
|
ch = fsa9285_read_reg(client, 0x08); //liulc1 add
|
|
fsa9285_write_reg(client, 0x8, (ch & 0xfe)); //liulc1 add
|
|
int len = 0;
|
|
len = lenovo_h_charger_uart_write("SC", 2);
|
|
printk("Before ww_debug tx %d len=%d\n", len, i);
|
|
mdelay(100);
|
|
|
|
lenovo_h_charger_uart_close();
|
|
lnw_gpio_set_alt(57, 0);
|
|
lnw_gpio_set_alt(61, 0);
|
|
gpio_direction_output(57, 1);
|
|
gpio_direction_output(61, 1);
|
|
msleep(1000);
|
|
ch = fsa9285_read_reg(client, 0x08); //liulc1 add
|
|
fsa9285_write_reg(client, 0x8, (ch | 0x01)); //liulc1 add
|
|
ch=read_vbus();
|
|
if(ch < 4000)
|
|
{
|
|
lenovo_h_charger_kthread_running=0;
|
|
dcp_plug_out(chip);
|
|
return;
|
|
|
|
}
|
|
if(ch > 10500)
|
|
break;
|
|
sw_status=fsa9285_read_reg(client, 0x01);
|
|
if(sw_status==0xf8)
|
|
{
|
|
lenovo_h_charger_kthread_running=0;
|
|
lenovo_h_charger_switch(0,chip); //switch the USB path to MIC-ON
|
|
return;
|
|
}
|
|
}
|
|
|
|
lenovo_h_charger_kthread_running=0;
|
|
//schedule_delayed_work(&chip->fsa9285_wrkr,HZ*60);
|
|
}
|
|
else
|
|
{
|
|
printk("==liulc1===switch to 12V OK\n");
|
|
lenovo_h_charger_kthread_running=0;
|
|
//schedule_delayed_work(&chip->fsa9285_wrkr,HZ*300);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
static int fsa9285_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
|
|
struct device *dev = &client->dev;
|
|
struct fsa9285_chip *chip;
|
|
int ret = 0;
|
|
int irq_gpio = 0;
|
|
struct proc_dir_entry *entry;
|
|
|
|
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
|
return -EIO;
|
|
|
|
ret = fsa9285_read_reg(client, LC8x_SWITCH_SET_MODE);
|
|
printk("%s, SWITCH_SET_MODE=0x%x\n", __func__, ret);
|
|
|
|
chip = kzalloc(sizeof(struct fsa9285_chip), GFP_KERNEL);
|
|
|
|
if (!chip) {
|
|
dev_err(&client->dev, "failed to allocate driver data\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
chip->client = client;
|
|
chip->pdata = dev->platform_data;
|
|
chip->pdata = fsa9285_platform_data();
|
|
i2c_set_clientdata(client, chip);
|
|
chip_ptr = chip;
|
|
|
|
/* register with extcon */
|
|
chip->edev = kzalloc(sizeof(struct extcon_dev), GFP_KERNEL);
|
|
|
|
if (!chip->edev) {
|
|
dev_err(&client->dev, "mem alloc failed\n");
|
|
ret = -ENOMEM;
|
|
goto extcon_mem_failed;
|
|
}
|
|
|
|
chip->edev->name = "fsa9285";
|
|
chip->edev->supported_cable = fsa9285_extcon_cable;
|
|
#if 1
|
|
ret = extcon_dev_register(chip->edev, &client->dev);
|
|
|
|
if (ret) {
|
|
dev_err(&client->dev, "extcon registration failed!!\n");
|
|
goto extcon_reg_failed;
|
|
}
|
|
|
|
/* OTG notification */
|
|
chip->otg = usb_get_phy(USB_PHY_TYPE_USB2);
|
|
if (!chip->otg) {
|
|
dev_warn(&client->dev, "Failed to get otg transceiver!!\n");
|
|
goto otg_reg_failed;
|
|
}
|
|
#endif
|
|
//chip->otg->a_bus_drop = fsa9285_vbus_cntl_state;
|
|
INIT_DELAYED_WORK(&chip->fsa9285_wrkr, fsa9285_detection_worker); //liulc1
|
|
ret = fsa9285_irq_init(chip);
|
|
|
|
get_id(chip);
|
|
charger_detect_retry(chip);
|
|
|
|
if (ret)
|
|
goto intr_reg_failed;
|
|
|
|
wake_lock_init(&chip->wakelock, WAKE_LOCK_SUSPEND,
|
|
"fsa_charger_wakelock");
|
|
/* device detection */
|
|
ret = fsa9285_detect_dev(chip);
|
|
|
|
if (ret < 0)
|
|
dev_warn(&client->dev, "probe: detection failed\n");
|
|
|
|
/* Init Runtime PM State */
|
|
pm_runtime_put_noidle(&chip->client->dev);
|
|
pm_schedule_suspend(&chip->client->dev, MSEC_PER_SEC);
|
|
//liulc1 add
|
|
entry = proc_create_data("driver/switch", S_IWUGO, NULL, &fops, chip);
|
|
if (!entry) {
|
|
printk("unable to create /proc entry-liulc2\n");
|
|
}
|
|
//liulc1 end
|
|
return 0;
|
|
|
|
intr_reg_failed:
|
|
if (client->irq)
|
|
free_irq(client->irq, chip);
|
|
/* WA for FFRD8 */
|
|
// if (chip->pdata->mux_gpio != -1)
|
|
// gpio_free(chip->pdata->mux_gpio);
|
|
/* gpio_req_failed: */
|
|
usb_put_phy(chip->otg);
|
|
otg_reg_failed:
|
|
extcon_dev_unregister(chip->edev);
|
|
extcon_reg_failed:
|
|
kfree(chip->edev);
|
|
extcon_mem_failed:
|
|
kfree(chip);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int fsa9285_remove(struct i2c_client *client)
|
|
{
|
|
struct fsa9285_chip *chip = i2c_get_clientdata(client);
|
|
|
|
if (chip->pdata->mux_gpio != -1)
|
|
gpio_free(chip->pdata->mux_gpio);
|
|
free_irq(client->irq, chip);
|
|
usb_put_phy(chip->otg);
|
|
extcon_dev_unregister(chip->edev);
|
|
kfree(chip->edev);
|
|
pm_runtime_get_noresume(&chip->client->dev);
|
|
kfree(chip);
|
|
return 0;
|
|
}
|
|
|
|
static void fsa9285_shutdown(struct i2c_client *client)
|
|
{
|
|
dev_dbg(&client->dev, "fsa9285 shutdown\n");
|
|
|
|
if (client->irq > 0)
|
|
disable_irq(client->irq);
|
|
return;
|
|
}
|
|
|
|
static int fsa9285_suspend(struct device *dev)
|
|
{
|
|
struct fsa9285_chip *chip = dev_get_drvdata(dev);
|
|
|
|
if (chip->client->irq > 0) {
|
|
disable_irq(chip->client->irq);
|
|
}
|
|
|
|
dev_dbg(dev, "%s called\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static int fsa9285_resume(struct device *dev)
|
|
{
|
|
struct fsa9285_chip *chip = dev_get_drvdata(dev);
|
|
|
|
if (chip->client->irq > 0) {
|
|
enable_irq(chip->client->irq);
|
|
}
|
|
|
|
dev_dbg(dev, "%s called\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static int fsa9285_runtime_suspend(struct device *dev)
|
|
{
|
|
dev_dbg(dev, "%s called\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static int fsa9285_runtime_resume(struct device *dev)
|
|
{
|
|
dev_dbg(dev, "%s called\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static int fsa9285_runtime_idle(struct device *dev)
|
|
{
|
|
dev_dbg(dev, "%s called\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops fsa9285_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(fsa9285_suspend,
|
|
fsa9285_resume)
|
|
SET_RUNTIME_PM_OPS(fsa9285_runtime_suspend,
|
|
fsa9285_runtime_resume,
|
|
fsa9285_runtime_idle)
|
|
};
|
|
|
|
static const struct i2c_device_id fsa9285_id[] = {
|
|
{"lc824206", 0},
|
|
// {"SFSA9285", 0},
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, fsa9285_id);
|
|
|
|
static const struct acpi_device_id acpi_fsa9285_id[] = {
|
|
// {"SFSA9285", 0},
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(acpi, acpi_fsa9285_id);
|
|
|
|
static struct i2c_driver fsa9285_i2c_driver = {
|
|
.driver = {
|
|
.name = "lc824206",
|
|
.owner = THIS_MODULE,
|
|
.pm = &fsa9285_pm_ops,
|
|
//.acpi_match_table = ACPI_PTR(acpi_fsa9285_id),
|
|
},
|
|
.probe = fsa9285_probe,
|
|
.remove = fsa9285_remove,
|
|
.id_table = fsa9285_id,
|
|
.shutdown = fsa9285_shutdown,
|
|
};
|
|
|
|
/*
|
|
* Module stuff
|
|
*/
|
|
static int __init fsa9285_extcon_early_init(void)
|
|
{
|
|
int i2c_busnum = 3;
|
|
struct i2c_board_info i2c_info;
|
|
void *pdata = NULL;
|
|
int ret;
|
|
|
|
memset(&i2c_info, 0, sizeof(i2c_info));
|
|
strlcpy(i2c_info.type, "lc824206", sizeof("lc824206"));
|
|
|
|
i2c_info.addr = 0x48;
|
|
|
|
pr_info("%s, I2C bus = %d, name = %16.16s, irq = 0x%2x, addr = 0x%x\n",
|
|
__func__,
|
|
i2c_busnum,
|
|
i2c_info.type,
|
|
i2c_info.irq,
|
|
i2c_info.addr);
|
|
|
|
ret = i2c_register_board_info(i2c_busnum, &i2c_info, 1);
|
|
|
|
printk("%s,ret = %d\n" ,__func__,ret );
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int __init fsa9285_extcon_late_init(void)
|
|
{
|
|
int i2c_busnum = 3;
|
|
struct i2c_board_info i2c_info;
|
|
void *pdata = NULL;
|
|
|
|
printk("-%s\n", __func__);
|
|
|
|
int ret = i2c_add_driver(&fsa9285_i2c_driver);
|
|
|
|
if (ret)
|
|
printk(KERN_ERR "Unable to register ULPMC i2c driver\n");
|
|
|
|
#if 0
|
|
memset(&i2c_info, 0, sizeof(i2c_info));
|
|
strlcpy(i2c_info.type, "lc824206", sizeof("lc824206"));
|
|
|
|
i2c_info.addr = 0x48;
|
|
|
|
pr_info("I2C bus = %d, name = %16.16s, irq = 0x%2x, addr = 0x%x\n",
|
|
i2c_busnum,
|
|
i2c_info.type,
|
|
i2c_info.irq,
|
|
i2c_info.addr);
|
|
|
|
i2c_register_board_info(i2c_busnum, &i2c_info, 1);
|
|
|
|
#endif
|
|
printk("-%s\n", __func__);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
fs_initcall(fsa9285_extcon_early_init);
|
|
late_initcall(fsa9285_extcon_late_init);
|
|
//module_init(fsa9285_extcon_init);
|
|
static void __exit fsa9285_extcon_exit(void)
|
|
{
|
|
i2c_del_driver(&fsa9285_i2c_driver);
|
|
}
|
|
module_exit(fsa9285_extcon_exit);
|
|
|
|
MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
|
|
MODULE_DESCRIPTION("FSA9285 extcon driver");
|
|
MODULE_LICENSE("GPL");
|