/*++
drivers/char/wmt-smb358.c - SMB358 Battery charging driver
Copyright (c) 2013 WonderMedia Technologies, Inc.
This program is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software Foundation,
either version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program. If not, see .
WonderMedia Technologies, Inc.
10F, 529, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C.
--*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
DEFINE_MUTEX(charger_mutex);
static int smb_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id);
static unsigned int smb_read(unsigned int reg);
static int smb_write(unsigned int reg, unsigned int value);
static unsigned int g_battery_charging_en;
static unsigned int g_i2cbus_id = 3;
#ifdef CONFIG_MTD_WMT_SF
extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen);
#endif
/*
#define SMB358 1
*/
#ifdef SMB358
#define smb_I2C_ADDR 0x6a /*SMB358*/
#define SMB_NAME "smb358"
#else
#define smb_I2C_ADDR 0x6a /*SMB347*/
#define SMB_NAME "smb347"
#endif
#define SUSGPIO0_EVENT_ID 0x10
static struct i2c_client *smb_client;
static struct workqueue_struct *smb_wq;
struct work_struct smb_work;
extern void pmc_enable_wakeup_event(unsigned int wakeup_event, unsigned int type);
extern void pmc_disable_wakeup_event(unsigned int wakeup_event);
extern int pmc_register_callback(unsigned int wakeup_event, void (*callback)(void *), void *callback_data);
extern int pmc_unregister_callback(unsigned int wakeup_event);
extern unsigned int pmc_get_wakeup_status(void);
extern int udc_register_redetection(unsigned int delay_ms, void (*callback)(void *), void *callback_data);
static void smb_enable_irq(void)
{
pmc_enable_wakeup_event(SUSGPIO0_EVENT_ID, 4);
}
static void smb_disable_irq(void)
{
pmc_disable_wakeup_event(SUSGPIO0_EVENT_ID);
}
static int smb_suspend(struct i2c_client *client, pm_message_t mesg)
{
int status;
int otg_status = 0;
printk("smb_suspend \n");
smb_disable_irq();
cancel_work_sync(&smb_work);
#ifndef SMB358
otg_status = smb_read(0x30);
if (otg_status & BIT4) {
status = smb_read(0x0A);
status &= ~0xC;
status |= 0x4; /*set to 250mA*/
smb_write(0x0A, status);
}
#endif
smb_write(0x30, 0x8a);/* OTG disable charging enable*/
return 0;
}
static int smb_resume(struct i2c_client *client)
{
unsigned int wakeup_sts;
unsigned int otg_status;
wakeup_sts = pmc_get_wakeup_status();
printk("smb resume \n");
otg_status =REG32_VAL(0xFE110000) & 0x00200000;
/*Do work function in OTG mode*/
if (!otg_status)
queue_work(smb_wq, &smb_work);
smb_enable_irq();
return 0;
}
static const struct i2c_device_id smb_i2c_id[] = {
{SMB_NAME, 1},
{}
};
MODULE_DEVICE_TABLE(i2c, smb_i2c_id);
static struct i2c_driver smb_i2c_driver = {
.driver = {
.name = SMB_NAME,
.owner = THIS_MODULE,
},
.probe = smb_i2c_probe,
.id_table = smb_i2c_id,
.suspend = smb_suspend,
.resume = smb_resume,
};
struct smb_conf_struct {
unsigned char bus_id;
struct i2c_client *client;
struct work_struct work;
struct i2c_adapter *i2c_adap;
};
static struct smb_conf_struct smb_conf;
static struct i2c_board_info __initdata smb_board_info[] = {
{
I2C_BOARD_INFO(SMB_NAME, smb_I2C_ADDR),
},
};
static void smb_irq_callback(void *data)
{
queue_work(smb_wq, &smb_work);
}
#ifdef CONFIG_MTD_WMT_SF
static void parse_arg(void)
{
int retval;
unsigned char buf[80];
unsigned char tmp_buf[80];
int varlen = 80;
char *varname = "wmt.bc.param";
int i = 0;
int j = 0;
retval = wmt_getsyspara(varname, buf, &varlen);
if (retval == 0) {
for (i = 0; i < 80; ++i) {
if (buf[i] == ':')
break;
g_battery_charging_en = (buf[i] - '0' == 1)?1:0;
if (g_battery_charging_en == 0)/*disable*/
return;
}
++i;
for (; i < 80; ++i) {
if (buf[i] == ':')
break;
tmp_buf[j] = buf[i];
++j;
}
if (!strncmp(tmp_buf,SMB_NAME,6))
g_battery_charging_en = 1;
else
g_battery_charging_en = 0;
++i;
g_i2cbus_id = buf[i] - '0';
} else
g_battery_charging_en = 0;
}
#endif
static int smb_modinit(void)
{
int ret = 0;
struct i2c_adapter *adapter = NULL;
struct i2c_client *client = NULL;
printk(" smb_modinit \n");
#ifdef CONFIG_MTD_WMT_SF
parse_arg();
#endif
if (!g_battery_charging_en) {
printk("NO SMB358/347 \n");
return -EIO;
}
smb_conf.bus_id = g_i2cbus_id;
adapter = i2c_get_adapter(smb_conf.bus_id);
smb_conf.i2c_adap = adapter;
if (adapter == NULL) {
printk("can not get smb i2c adapter, client address error");
return -ENODEV;
}
if ((client=i2c_new_device(adapter, smb_board_info)) == NULL) {
printk("allocate smb i2c client failed");
return -ENOMEM;
}
ret = i2c_add_driver(&smb_i2c_driver);
if (ret)
printk("SMB 358/347 i2c fail\n");
return ret;
}
module_init(smb_modinit);
static void smb_work_func(struct work_struct *work)
{
int status;
int output_current_status = 0;
mutex_lock(&charger_mutex);
#ifndef SMB358
status = smb_read(0x0A);
#endif
status =REG32_VAL(0xFE110000) & 0x00200000;
if (!status ) {
printk("smb insert 0x30=0x98 with 500mA\n");
smb_write(0x30, 0x98); /* OTG enable charging disbale*/
#ifndef SMB358
output_current_status = smb_read(0x0A);
output_current_status &= ~0xC;
output_current_status |= 0x8; /*set to 500mA*/
smb_write(0x0A, output_current_status);
#endif
} else {
#ifndef SMB358
output_current_status = smb_read(0x0A);
output_current_status &= ~0xC;
output_current_status |= 0x4; /* set to 250mA*/
smb_write(0x0A, output_current_status);
#endif
printk("smb not insert 0x30=0x8a\n");
smb_write(0x30, 0x8a); /* OTG disable charging enable*/
}
mutex_unlock(&charger_mutex);
}
static void smb_power_src_redetection(void *data)
{
int ret = 0;
mutex_lock(&charger_mutex);
ret = smb_write(0x30, 0xC0);
ret = smb_write(0x04, 0x08);
ret = smb_write(0x04, 0x0E);
mutex_unlock(&charger_mutex);
return;
}
static int smb_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct smb_conf_struct *smb;
int ret = 0;
int reg;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
printk("SMB I2C_FUNC_I2C FAILED\n");
return -ENODEV;
}
smb = kzalloc(sizeof(*smb), GFP_KERNEL);
if (smb == NULL) {
printk("SMB FAIL\n");
return -ENOMEM;
}
smb->client = client;
smb_client = client;
i2c_set_clientdata(client,smb);
printk(" smb_i2c_probe \n");
smb_wq = create_workqueue("smb_wq");
if(smb_wq == NULL) {
printk("smb work fail \n");
return -ENOMEM;
}
INIT_WORK(&smb_work,smb_work_func);
reg = REG32_VAL(0xFE110000) & 0x00200000; /*read sus_gpio0 status*/
if (!reg) {
printk("smb insert 0x30=0x98\n");
smb_write(0x30, 0x98); /*OTG enable charging disbale*/
} else {
printk("smb not insert\n");
if (smb_read(0x3E) & 0x4) {/*USB_PC*/
printk("re-detection power source\n");
smb_power_src_redetection(client);
}
smb_write(0x30, 0x8a); /* OTG disable charging enable*/
}
/*
udc_register_redetection(1200, smb_power_src_redetection, client);
*/
ret = pmc_register_callback(SUSGPIO0_EVENT_ID, smb_irq_callback, client);
if (ret == 0)
smb_enable_irq();
return ret;
}
static int smb_write(unsigned int reg, unsigned int value)
{
int count;
struct i2c_msg msg[1];
unsigned char buf[2];
count=10;
while (count--) {
buf[0] = reg;
buf[1] = value;
msg[0].addr = smb_I2C_ADDR;
msg[0].flags = 0 ;
msg[0].flags &= ~(I2C_M_RD);
msg[0].len = 2;
msg[0].buf = buf;
if (i2c_transfer(smb_conf.i2c_adap, msg, ARRAY_SIZE(msg)) == ARRAY_SIZE(msg))
return 0;
}
printk(" error: i2c write reg[%02x]=%02x ", reg, value);
return -1;
}
static unsigned int smb_read(unsigned int reg)
{
unsigned char buf[1];
struct i2c_msg msg[2];
msg[0].addr = smb_I2C_ADDR;
msg[0].flags = (2 | I2C_M_NOSTART);
msg[0].len = 1;
msg[0].buf = (unsigned char *)®
msg[1].addr = smb_I2C_ADDR;
msg[1].flags = (I2C_M_RD | I2C_M_NOSTART);
msg[1].len = 1;
msg[1].buf = buf;
if (i2c_transfer(smb_conf.i2c_adap, msg, ARRAY_SIZE(msg)) == ARRAY_SIZE(msg))
return buf[0];
printk(" error: i2c read reg[%02x]", reg);
return -1;
}
static void __exit smb_exit(void)
{
i2c_del_driver(&smb_i2c_driver);
}
module_exit(smb_exit);
MODULE_DESCRIPTION("WMT SMB358/SMB347 driver");
MODULE_AUTHOR("WonderMedia Technologies, Inc.");
MODULE_LICENSE("GPL");