blob: b04701f2d53a0482f4452150cb5f30a4f55eb4e9 [file] [log] [blame]
/*
* drivers/power/Smb345-charger.c
*
* Charger driver for Summit SMB345
*
* Copyright (c) 2012, ASUSTek 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.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/power_supply.h>
#include <linux/platform_device.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/smb345-charger.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/usb/otg.h>
#include <linux/workqueue.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/ioctl.h>
#include <asm/uaccess.h>
#include <asm/mach-types.h>
#define smb345_CHARGE 0x00
#define smb345_CHRG_CRNTS 0x01
#define smb345_VRS_FUNC 0x02
#define smb345_FLOAT_VLTG 0x03
#define smb345_CHRG_CTRL 0x04
#define smb345_STAT_TIME_CTRL 0x05
#define smb345_PIN_CTRL 0x06
#define smb345_THERM_CTRL 0x07
#define smb345_SYSOK_USB3 0x08
#define smb345_CTRL_REG 0x09
#define smb345_OTG_TLIM_REG 0x0A
#define smb345_HRD_SFT_TEMP 0x0B
#define smb345_FAULT_INTR 0x0C
#define smb345_STS_INTR_1 0x0D
#define smb345_I2C_ADDR 0x0E
#define smb345_IN_CLTG_DET 0x10
#define smb345_STS_INTR_2 0x11
/* Command registers */
#define smb345_CMD_REG 0x30
#define smb345_CMD_REG_B 0x31
#define smb345_CMD_REG_c 0x33
/* Interrupt Status registers */
#define smb345_INTR_STS_A 0x35
#define smb345_INTR_STS_B 0x36
#define smb345_INTR_STS_C 0x37
#define smb345_INTR_STS_D 0x38
#define smb345_INTR_STS_E 0x39
#define smb345_INTR_STS_F 0x3A
/* Status registers */
#define smb345_STS_REG_A 0x3B
#define smb345_STS_REG_B 0x3C
#define smb345_STS_REG_C 0x3D
#define smb345_STS_REG_D 0x3E
#define smb345_STS_REG_E 0x3F
/* APQ8064 GPIO pin definition */
#define APQ_AP_CHAR 22
#define APQ_AP_ACOK 23
#define smb345_ENABLE_WRITE 1
#define smb345_DISABLE_WRITE 0
#define ENABLE_WRT_ACCESS 0x80
#define ENABLE_APSD 0x04
#define PIN_CTRL 0x10
#define PIN_ACT_LOW 0x20
#define ENABLE_CHARGE 0x02
#define USBIN 0x80
#define APSD_OK 0x08
#define APSD_RESULT 0x07
#define FLOAT_VOLT_MASK 0x3F
#define ENABLE_PIN_CTRL_MASK 0x60
#define HOT_LIMIT_MASK 0x33
#define BAT_OVER_VOLT_MASK 0x40
#define STAT_OUTPUT_EN 0x20
#define GPIO_AC_OK APQ_AP_ACOK
#define WPC_DEBOUNCE_INTERVAL (1 * HZ)
#define WPC_SET_CURT_INTERVAL (2 * HZ)
#define WPC_INIT_DET_INTERVAL (22 * HZ)
#define WPC_SET_CURT_LIMIT_CNT 6
#define BAT_Cold_Limit 0
#define BAT_Hot_Limit 45
#define BAT_Mid_Temp_Wired 45
#define BAT_Mid_Temp_Wireless 40
#define FLOAT_VOLT 0x2A
#define FLOAT_VOLT_LOW 0x1E
#define FLOAT_VOLT_43V 0x28
#define FLOAT_VOLT_LOW_DECIMAL 4110000
#define THERMAL_RULE1 1
#define THERMAL_RULE2 2
/* Functions declaration */
extern int bq27541_battery_callback(unsigned usb_cable_state);
extern int bq27541_wireless_callback(unsigned wireless_state);
extern void touch_callback(unsigned cable_status);
static ssize_t smb345_reg_show(struct device *dev, struct device_attribute *attr, char *buf);
/* Global variables */
static struct smb345_charger *charger;
static struct workqueue_struct *smb345_wq;
struct wake_lock charger_wakelock;
static int charge_en_flag = 1;
struct wake_lock wireless_wakelock;
bool wireless_on;
bool otg_on;
extern int ac_on;
extern int usb_on;
static bool wpc_en;
static bool disable_DCIN;
/* Sysfs interface */
static DEVICE_ATTR(reg_status, S_IWUSR | S_IRUGO, smb345_reg_show, NULL);
static struct attribute *smb345_attributes[] = {
&dev_attr_reg_status.attr,
NULL
};
static const struct attribute_group smb345_group = {
.attrs = smb345_attributes,
};
static int smb345_read(struct i2c_client *client, int reg)
{
int ret;
ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0)
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
return ret;
}
static int smb345_write(struct i2c_client *client, int reg, u8 value)
{
int ret;
ret = i2c_smbus_write_byte_data(client, reg, value);
if (ret < 0)
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
return ret;
}
static int smb345_update_reg(struct i2c_client *client, int reg, u8 value)
{
int ret, retval;
retval = smb345_read(client, reg);
if (retval < 0) {
dev_err(&client->dev, "%s: err %d\n", __func__, retval);
return retval;
}
ret = smb345_write(client, reg, retval | value);
if (ret < 0) {
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
return ret;
}
return ret;
}
static int smb345_clear_reg(struct i2c_client *client, int reg, u8 value)
{
int ret, retval;
retval = smb345_read(client, reg);
if (retval < 0) {
dev_err(&client->dev, "%s: err %d\n", __func__, retval);
return retval;
}
ret = smb345_write(client, reg, retval & (~value));
if (ret < 0) {
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
return ret;
}
return ret;
}
int smb345_volatile_writes(struct i2c_client *client, uint8_t value)
{
int ret = 0;
if (value == smb345_ENABLE_WRITE) {
/* Enable volatile write to config registers */
ret = smb345_update_reg(client, smb345_CMD_REG,
ENABLE_WRT_ACCESS);
if (ret < 0) {
dev_err(&client->dev, "%s(): Failed in writing"
"register 0x%02x\n", __func__, smb345_CMD_REG);
return ret;
}
} else {
ret = smb345_read(client, smb345_CMD_REG);
if (ret < 0) {
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
return ret;
}
ret = smb345_write(client, smb345_CMD_REG, ret & (~(1<<7)));
if (ret < 0) {
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
return ret;
}
}
return ret;
}
int wireless_is_plugged(void)
{
if (charger->wpc_pok_gpio != -1)
return !(gpio_get_value(charger->wpc_pok_gpio));
else
return 0;
}
static int smb345_pin_control(bool state)
{
struct i2c_client *client = charger->client;
u8 ret = 0;
mutex_lock(&charger->pinctrl_lock);
if (state) {
/*Pin Controls -active low */
ret = smb345_update_reg(client, smb345_PIN_CTRL, PIN_ACT_LOW);
if (ret < 0) {
dev_err(&client->dev, "%s(): Failed to"
"enable charger\n", __func__);
}
} else {
/*Pin Controls -active high */
ret = smb345_clear_reg(client, smb345_PIN_CTRL, PIN_ACT_LOW);
if (ret < 0) {
dev_err(&client->dev, "%s(): Failed to"
"disable charger\n", __func__);
}
}
mutex_unlock(&charger->pinctrl_lock);
return ret;
}
int smb345_charger_enable(bool state)
{
struct i2c_client *client = charger->client;
u8 ret = 0;
ret = smb345_volatile_writes(client, smb345_ENABLE_WRITE);
if (ret < 0) {
dev_err(&client->dev, "%s() error in configuring charger..\n",
__func__);
goto error;
}
charge_en_flag = state;
smb345_pin_control(state);
ret = smb345_volatile_writes(client, smb345_DISABLE_WRITE);
if (ret < 0) {
dev_err(&client->dev, "%s() error in configuring charger..\n",
__func__);
goto error;
}
error:
return ret;
}
EXPORT_SYMBOL_GPL(smb345_charger_enable);
int smb345_vflt_setting(void)
{
struct i2c_client *client = charger->client;
u8 ret = 0, setting;
ret = smb345_volatile_writes(client, smb345_ENABLE_WRITE);
if (ret < 0) {
dev_err(&client->dev, "%s() error in smb345 volatile writes \n", __func__);
goto error;
}
ret = smb345_read(client, smb345_FLOAT_VLTG);
if (ret < 0) {
dev_err(&client->dev, "%s() error in smb345 read \n", __func__);
goto error;
}
setting = ret & FLOAT_VOLT_MASK;
if (setting != FLOAT_VOLT_43V) {
setting = ret & (~FLOAT_VOLT_MASK);
setting |= FLOAT_VOLT_43V;
SMB_NOTICE("Set Float Volt, retval=%x setting=%x\n", ret, setting);
ret = smb345_write(client, smb345_FLOAT_VLTG, setting);
if (ret < 0) {
dev_err(&client->dev, "%s() error in smb345 write \n", __func__);
goto error;
}
} else
SMB_NOTICE("Bypass set Float Volt=%x\n", ret);
ret = smb345_volatile_writes(client, smb345_DISABLE_WRITE);
if (ret < 0) {
dev_err(&client->dev, "%s() error in smb345 volatile writes \n", __func__);
goto error;
}
error:
return ret;
}
int
smb345_set_InputCurrentlimit(struct i2c_client *client, u32 current_setting)
{
int ret = 0, retval;
u8 setting = 0;
wake_lock(&charger_wakelock);
ret = smb345_volatile_writes(client, smb345_ENABLE_WRITE);
if (ret < 0) {
dev_err(&client->dev, "%s() error in configuring charger..\n",
__func__);
goto error;
}
if (charge_en_flag)
smb345_pin_control(0);
retval = smb345_read(client, smb345_VRS_FUNC);
if (retval < 0) {
dev_err(&client->dev, "%s(): Failed in reading 0x%02x",
__func__, smb345_VRS_FUNC);
goto error;
}
setting = retval & (~(BIT(4)));
SMB_NOTICE("Disable AICL, retval=%x setting=%x\n", retval, setting);
ret = smb345_write(client, smb345_VRS_FUNC, setting);
if (ret < 0) {
dev_err(&client->dev, "%s(): Failed in writing 0x%02x to register"
"0x%02x\n", __func__, setting, smb345_VRS_FUNC);
goto error;
}
retval = smb345_read(client, smb345_CHRG_CRNTS);
if (retval < 0) {
dev_err(&client->dev, "%s(): Failed in reading 0x%02x",
__func__, smb345_CHRG_CRNTS);
goto error;
}
setting = retval & 0xF0;
if(current_setting == 2000)
setting |= 0x07;
else if(current_setting == 1800)
setting |= 0x06;
else if (current_setting == 1200)
setting |= 0x04;
else if(current_setting == 900)
setting |= 0x03;
else if(current_setting == 500)
setting |= 0x01;
else
setting |= 0x07;
SMB_NOTICE("Set ICL=%u retval =%x setting=%x\n", current_setting, retval, setting);
ret = smb345_write(client, smb345_CHRG_CRNTS, setting);
if (ret < 0) {
dev_err(&client->dev, "%s(): Failed in writing 0x%02x to register"
"0x%02x\n", __func__, setting, smb345_CHRG_CRNTS);
goto error;
}
if(current_setting == 2000)
charger->curr_limit = 2000;
else if(current_setting == 1800)
charger->curr_limit = 1800;
else if (current_setting == 1200)
charger->curr_limit = 1200;
else if(current_setting == 900)
charger->curr_limit = 900;
else if(current_setting == 500)
charger->curr_limit = 500;
else
charger->curr_limit = 2000;
if (current_setting > 900) {
charger->time_of_1800mA_limit = jiffies;
} else{
charger->time_of_1800mA_limit = 0;
}
retval = smb345_read(client, smb345_VRS_FUNC);
if (retval < 0) {
dev_err(&client->dev, "%s(): Failed in reading 0x%02x",
__func__, smb345_VRS_FUNC);
goto error;
}
setting = retval | BIT(4);
SMB_NOTICE("Re-enable AICL, setting=%x\n", setting);
msleep(20);
ret = smb345_write(client, smb345_VRS_FUNC, setting);
if (ret < 0) {
dev_err(&client->dev, "%s(): Failed in writing 0x%02x to register"
"0x%02x\n", __func__, setting, smb345_VRS_FUNC);
goto error;
}
if (charge_en_flag)
smb345_pin_control(1);
ret = smb345_volatile_writes(client, smb345_DISABLE_WRITE);
if (ret < 0) {
dev_err(&client->dev, "%s() error in configuring charger..\n",
__func__);
goto error;
}
error:
wake_unlock(&charger_wakelock);
return ret;
}
static irqreturn_t smb345_inok_isr(int irq, void *dev_id)
{
SMB_NOTICE("VBUS_DET = %s\n", gpio_get_value(GPIO_AC_OK) ? "H" : "L");
return IRQ_HANDLED;
}
static irqreturn_t smb345_wireless_isr(int irq, void *dev_id)
{
struct smb345_charger *smb = dev_id;
queue_delayed_work(smb345_wq, &smb->wireless_isr_work, WPC_DEBOUNCE_INTERVAL);
return IRQ_HANDLED;
}
static int smb345_wireless_irq(struct smb345_charger *smb)
{
int err = 0 ;
unsigned gpio = charger->wpc_pok_gpio;
unsigned irq_num = gpio_to_irq(gpio);
err = gpio_request(gpio, "wpc_pok");
if (err)
SMB_ERR("gpio %d request failed\n", gpio);
err = gpio_direction_input(gpio);
if (err)
SMB_ERR("gpio %d unavaliable for input\n", gpio);
err = request_irq(irq_num, smb345_wireless_isr,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_SHARED,
"wpc_pok", smb);
if (err < 0) {
SMB_ERR("%s irq %d request failed\n", "wpc_pok", irq_num);
goto fault ;
}
enable_irq_wake(irq_num);
SMB_NOTICE("GPIO pin irq %d requested ok, wpc_pok = %s\n", irq_num,
gpio_get_value(gpio) ? "H" : "L");
return 0;
fault:
gpio_free(gpio);
return err;
}
int
smb345_set_WCInputCurrentlimit(struct i2c_client *client, u32 current_setting)
{
int ret = 0, retval;
u8 setting;
charger->wpc_curr_limit_count++;
ret = smb345_volatile_writes(client, smb345_ENABLE_WRITE);
if (ret < 0) {
dev_err(&client->dev, "%s() error in configuring charger..\n",
__func__);
goto error;
}
if (current_setting != 0) {
retval = smb345_read(client, smb345_CHRG_CRNTS);
if (retval < 0) {
dev_err(&client->dev, "%s(): Failed in reading 0x%02x",
__func__, smb345_CHRG_CRNTS);
goto error;
}
setting = retval & 0x0F;
if (current_setting == 2000)
setting |= 0x70;
else if (current_setting == 1800)
setting |= 0x60;
else if (current_setting == 1200)
setting |= 0x40;
else if (current_setting == 900)
setting |= 0x30;
else if (current_setting == 700)
setting |= 0x20;
else if (current_setting == 500)
setting |= 0x10;
else if (current_setting == 300)
setting |= 0x00;
else
setting |= 0x20;
SMB_NOTICE("Set ICL=%u retval =%x setting=%x\n",
current_setting, retval, setting);
ret = smb345_write(client, smb345_CHRG_CRNTS, setting);
if (ret < 0) {
dev_err(&client->dev, "%s(): Failed in writing 0x%02x to register"
"0x%02x\n", __func__, setting, smb345_CHRG_CRNTS);
goto error;
}
charger->wpc_curr_limit = current_setting;
}
if (current_setting == 300) {
retval = smb345_read(client, smb345_VRS_FUNC);
if (retval < 0) {
dev_err(&client->dev, "%s(): Failed in reading 0x%02x",
__func__, smb345_VRS_FUNC);
goto error;
}
setting = retval & (~(BIT(4)));
SMB_NOTICE("Disable AICL, retval=%x setting=%x\n", retval, setting);
ret = smb345_write(client, smb345_VRS_FUNC, setting);
if (ret < 0) {
dev_err(&client->dev, "%s(): Failed in writing 0x%02x to register"
"0x%02x\n", __func__, setting, smb345_VRS_FUNC);
goto error;
}
}
ret = smb345_volatile_writes(client, smb345_DISABLE_WRITE);
if (ret < 0) {
dev_err(&client->dev, "%s() error in configuring charger..\n",
__func__);
goto error;
}
error:
return ret;
}
static void wireless_set(void)
{
wireless_on = true;
if (delayed_work_pending(&charger->wireless_set_current_work))
cancel_delayed_work(&charger->wireless_set_current_work);
wake_lock(&wireless_wakelock);
charger->wpc_curr_limit = 300;
charger->wpc_curr_limit_count = 0;
smb345_set_WCInputCurrentlimit(charger->client, 300);
smb345_vflt_setting();
bq27541_wireless_callback(wireless_on);
queue_delayed_work(smb345_wq, &charger->wireless_set_current_work, WPC_SET_CURT_INTERVAL);
}
static void wireless_reset(void)
{
wireless_on = false;
if (delayed_work_pending(&charger->wireless_set_current_work))
cancel_delayed_work(&charger->wireless_set_current_work);
charger->wpc_curr_limit = 300;
charger->wpc_curr_limit_count = 0;
if (ac_on) {
smb345_set_InputCurrentlimit(charger->client, 1200);
smb345_vflt_setting();
}
bq27541_wireless_callback(wireless_on);
wake_unlock(&wireless_wakelock);
}
static void wireless_isr_work_function(struct work_struct *dat)
{
if (delayed_work_pending(&charger->wireless_isr_work))
cancel_delayed_work(&charger->wireless_isr_work);
SMB_NOTICE("wireless state = %d\n", wireless_is_plugged());
if (otg_on) {
SMB_NOTICE("bypass wireless isr due to otg_on\n");
return;
}
if (wireless_is_plugged())
wireless_set();
else
wireless_reset();
}
static void wireless_det_work_function(struct work_struct *dat)
{
if (otg_on) {
SMB_NOTICE("bypass wireless isr due to otg_on\n");
return;
}
if (wireless_is_plugged())
wireless_set();
}
static void wireless_set_current_function(struct work_struct *dat)
{
if (delayed_work_pending(&charger->wireless_set_current_work))
cancel_delayed_work(&charger->wireless_set_current_work);
if (charger->wpc_curr_limit == 700 || charger->wpc_curr_limit_count >= WPC_SET_CURT_LIMIT_CNT)
return;
else if (charger->wpc_curr_limit == 300)
smb345_set_WCInputCurrentlimit(charger->client, 500);
else if (charger->wpc_curr_limit == 500)
smb345_set_WCInputCurrentlimit(charger->client, 700);
queue_delayed_work(smb345_wq, &charger->wireless_set_current_work, WPC_SET_CURT_INTERVAL);
}
void reconfig_AICL(void)
{
struct i2c_client *client = charger->client;
if (ac_on && !gpio_get_value(GPIO_AC_OK)) {
int retval;
retval = smb345_read(client, smb345_STS_REG_E);
if (retval < 0)
dev_err(&client->dev, "%s(): Failed in reading 0x%02x",
__func__, smb345_STS_REG_E);
else {
SMB_NOTICE("Status Reg E=0x%02x\n", retval);
if ((retval & 0xF) <= 0x1) {
SMB_NOTICE("reconfig input current limit\n");
smb345_set_InputCurrentlimit(client, 1200);
}
}
}
}
EXPORT_SYMBOL(reconfig_AICL);
static int smb345_inok_irq(struct smb345_charger *smb)
{
int err = 0 ;
unsigned gpio = GPIO_AC_OK;
unsigned irq_num = gpio_to_irq(gpio);
err = gpio_request(gpio, "smb345_inok");
if (err) {
SMB_ERR("gpio %d request failed \n", gpio);
}
err = gpio_direction_input(gpio);
if (err) {
SMB_ERR("gpio %d unavaliable for input \n", gpio);
}
err = request_irq(irq_num, smb345_inok_isr, IRQF_TRIGGER_FALLING |IRQF_TRIGGER_RISING|IRQF_SHARED,
"smb345_inok", smb);
if (err < 0) {
SMB_ERR("%s irq %d request failed \n","smb345_inok", irq_num);
goto fault ;
}
enable_irq_wake(irq_num);
SMB_NOTICE("GPIO pin irq %d requested ok, smb345_INOK = %s\n", irq_num, gpio_get_value(gpio)? "H":"L");
return 0;
fault:
gpio_free(gpio);
return err;
}
static int smb345_configure_otg(struct i2c_client *client)
{
int ret = 0;
/*Enable volatile writes to registers and Allow fast charge */
ret = smb345_write(client, smb345_CMD_REG, 0xc0);
if (ret < 0) {
dev_err(&client->dev, "%s(): Failed in writing"
"register 0x%02x\n", __func__, smb345_CMD_REG);
goto error;
}
/* Change "OTG output current limit" to 250mA */
ret = smb345_write(client, smb345_OTG_TLIM_REG, 0x34);
if (ret < 0) {
dev_err(&client->dev, "%s(): Failed in writing"
"register 0x%02x\n", __func__, smb345_OTG_TLIM_REG);
goto error;
}
/* Enable OTG */
ret = smb345_update_reg(client, smb345_CMD_REG, 0x10);
if (ret < 0) {
dev_err(&client->dev, "%s: Failed in writing register"
"0x%02x\n", __func__, smb345_CMD_REG);
goto error;
}
/* Change "OTG output current limit" from 250mA to 750mA */
ret = smb345_update_reg(client, smb345_OTG_TLIM_REG, 0x08);
if (ret < 0) {
dev_err(&client->dev, "%s: Failed in writing register"
"0x%02x\n", __func__, smb345_OTG_TLIM_REG);
goto error;
}
/* Change OTG to Pin control */
ret = smb345_write(client, smb345_CTRL_REG, 0x65);
if (ret < 0) {
dev_err(&client->dev, "%s(): Failed in writing"
"register 0x%02x\n", __func__, smb345_CTRL_REG);
goto error;
}
/* Disable volatile writes to registers */
ret = smb345_volatile_writes(client, smb345_DISABLE_WRITE);
if (ret < 0) {
dev_err(&client->dev, "%s error in configuring OTG..\n",
__func__);
goto error;
}
return 0;
error:
return ret;
}
void smb345_otg_status(bool on)
{
struct i2c_client *client = charger->client;
int ret;
SMB_NOTICE("otg function: %s\n", on ? "on" : "off");
if (on) {
otg_on = true;
/* ENABLE OTG */
ret = smb345_configure_otg(client);
if (ret < 0) {
dev_err(&client->dev, "%s() error in configuring"
"otg..\n", __func__);
return;
}
if (wireless_is_plugged())
wireless_reset();
return;
} else
otg_on = false;
if (wireless_is_plugged())
wireless_set();
}
EXPORT_SYMBOL_GPL(smb345_otg_status);
int smb345_float_volt_set(unsigned int val)
{
struct i2c_client *client = charger->client;
int ret = 0, retval;
if (val > 4500 || val < 3500) {
SMB_ERR("%s(): val=%d is out of range !\n",__func__, val);
}
printk("%s(): val=%d\n",__func__, val);
ret = smb345_volatile_writes(client, smb345_ENABLE_WRITE);
if (ret < 0) {
dev_err(&client->dev, "%s() charger enable write error..\n", __func__);
goto fault;
}
retval = smb345_read(client, smb345_FLOAT_VLTG);
if (retval < 0) {
dev_err(&client->dev, "%s(): Failed in reading 0x%02x",
__func__, smb345_FLOAT_VLTG);
goto fault;
}
retval = retval & (~FLOAT_VOLT_MASK);
val = clamp_val(val, 3500, 4500) - 3500;
val /= 20;
retval |= val;
ret = smb345_write(client, smb345_FLOAT_VLTG, retval);
if (ret < 0) {
dev_err(&client->dev, "%s(): Failed in writing"
"register 0x%02x\n", __func__, smb345_FLOAT_VLTG);
goto fault;
}
ret = smb345_volatile_writes(client, smb345_DISABLE_WRITE);
if (ret < 0) {
dev_err(&client->dev, "%s() charger disable write error..\n", __func__);
}
return 0;
fault:
return ret;
}
EXPORT_SYMBOL_GPL(smb345_float_volt_set);
int usb_cable_type_detect(unsigned int chgr_type)
{
struct i2c_client *client = charger->client;
int success = 0;
mutex_lock(&charger->usb_lock);
if (chgr_type == CHARGER_NONE) {
SMB_NOTICE("INOK=H\n");
if (wpc_en) {
if (disable_DCIN) {
SMB_NOTICE("enable wpc_pok, enable DCIN\n");
disable_DCIN = false;
enable_irq_wake(gpio_to_irq(charger->wpc_pok_gpio));
enable_irq(gpio_to_irq(charger->wpc_pok_gpio));
gpio_set_value(charger->wpc_en1, 0);
gpio_set_value(charger->wpc_en2, 0);
}
}
success = bq27541_battery_callback(non_cable);
touch_callback(non_cable);
} else {
SMB_NOTICE("INOK=L\n");
if (chgr_type == CHARGER_SDP) {
SMB_NOTICE("Cable: SDP\n");
smb345_vflt_setting();
success = bq27541_battery_callback(usb_cable);
touch_callback(usb_cable);
} else {
if (chgr_type == CHARGER_CDP) {
SMB_NOTICE("Cable: CDP\n");
} else if (chgr_type == CHARGER_DCP) {
SMB_NOTICE("Cable: DCP\n");
} else if (chgr_type == CHARGER_OTHER) {
SMB_NOTICE("Cable: OTHER\n");
} else if (chgr_type == CHARGER_ACA) {
SMB_NOTICE("Cable: ACA\n");
} else {
SMB_NOTICE("Cable: TBD\n");
smb345_vflt_setting();
success = bq27541_battery_callback(usb_cable);
touch_callback(usb_cable);
goto done;
}
smb345_set_InputCurrentlimit(client, 1200);
smb345_vflt_setting();
success = bq27541_battery_callback(ac_cable);
touch_callback(ac_cable);
if (wpc_en) {
if (delayed_work_pending(&charger->wireless_set_current_work))
cancel_delayed_work(&charger->wireless_set_current_work);
if (!disable_DCIN) {
SMB_NOTICE("AC cable detect, disable wpc_pok, disable DCIN");
disable_DCIN = true;
disable_irq(gpio_to_irq(charger->wpc_pok_gpio));
disable_irq_wake(gpio_to_irq(charger->wpc_pok_gpio));
gpio_set_value(charger->wpc_en1, 1);
gpio_set_value(charger->wpc_en2, 1);
mdelay(200);
if (wireless_on)
wireless_reset();
}
}
}
}
done:
mutex_unlock(&charger->usb_lock);
return success;
}
EXPORT_SYMBOL_GPL(usb_cable_type_detect);
/* Sysfs function */
static ssize_t smb345_reg_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct i2c_client *client = charger->client;
uint8_t config_reg[15], cmd_reg[2], status_reg[11];
char tmp_buf[64];
int i, cfg_ret, cmd_ret, sts_ret = 0;
cfg_ret = i2c_smbus_read_i2c_block_data(client, smb345_CHARGE, 15, config_reg);
cmd_ret = i2c_smbus_read_i2c_block_data(client, smb345_CMD_REG, 2, cmd_reg);
sts_ret = i2c_smbus_read_i2c_block_data(client, smb345_INTR_STS_A, 11, status_reg);
sprintf(tmp_buf, "SMB34x Configuration Registers Detail\n"
"==================\n");
strcpy(buf, tmp_buf);
if (cfg_ret > 0) {
for(i=0;i<=14;i++) {
sprintf(tmp_buf, "Reg%02xh:\t0x%02x\n", i, config_reg[i]);
strcat(buf, tmp_buf);
}
}
if (cmd_ret > 0) {
for(i=0;i<=1;i++) {
sprintf(tmp_buf, "Reg%02xh:\t0x%02x\n", 48+i, cmd_reg[i]);
strcat(buf, tmp_buf);
}
}
if (sts_ret > 0) {
for(i=0;i<=10;i++) {
sprintf(tmp_buf, "Reg%02xh:\t0x%02x\n", 53+i, status_reg[i]);
strcat(buf, tmp_buf);
}
}
return strlen(buf);
}
static int smb345_otg_setting(struct i2c_client *client)
{
int ret;
/*Enable volatile writes to registers and Allow fast charge */
ret = smb345_update_reg(client, smb345_CMD_REG, 0xc0);
if (ret < 0) {
dev_err(&client->dev, "%s(): Failed in writing"
"register 0x%02x\n", __func__, smb345_CMD_REG);
goto error;
}
/* Change OTG to I2C control */
ret = smb345_update_reg(client, smb345_CTRL_REG, 0x25);
if (ret < 0) {
dev_err(&client->dev, "%s(): Failed in writing"
"register 0x%02x\n", __func__, smb345_CTRL_REG);
goto error;
}
/*Enable volatile writes to registers and Allow fast charge */
ret = smb345_update_reg(client, smb345_CMD_REG, 0xc0);
if (ret < 0) {
dev_err(&client->dev, "%s(): Failed in writing"
"register 0x%02x\n", __func__, smb345_CMD_REG);
goto error;
}
/* Change "OTG output current limit" to 250mA */
ret = smb345_update_reg(client, smb345_OTG_TLIM_REG, 0x34);
if (ret < 0) {
dev_err(&client->dev, "%s(): Failed in writing"
"register 0x%02x\n", __func__, smb345_OTG_TLIM_REG);
goto error;
}
/* Disable volatile writes to registers */
ret = smb345_volatile_writes(client, smb345_DISABLE_WRITE);
if (ret < 0) {
dev_err(&client->dev, "%s error in configuring OTG..\n",
__func__);
goto error;
}
return 0;
error:
return ret;
}
static int smb345_wireless_en_config(struct smb345_charger *smb)
{
int err = 0 ;
err = gpio_request(charger->wpc_en1, "WPC_EN1");
if (err)
return -ENODEV;
err = gpio_request(charger->wpc_en2, "WPC_EN2");
if (err)
goto fault;
gpio_set_value(charger->wpc_en1, 0);
gpio_set_value(charger->wpc_en2, 0);
SMB_NOTICE("GPIO pin %d, %d requested ok, wpc_en1 = %s, wpc_en2 = %s\n",
charger->wpc_en1, charger->wpc_en2, gpio_get_value(charger->wpc_en1) ? "H" : "L",
gpio_get_value(charger->wpc_en2) ? "H" : "L");
return 0;
fault:
gpio_free(charger->wpc_en1);
return err;
}
int smb345_config_thermal_suspend(void)
{
struct i2c_client *client = charger->client;
int ret = 0, retval, setting = 0;
ret = smb345_volatile_writes(client, smb345_ENABLE_WRITE);
if (ret < 0) {
dev_err(&client->dev, "%s() charger enable write error..\n", __func__);
goto error;
}
retval = smb345_read(client, smb345_HRD_SFT_TEMP);
if (retval < 0) {
dev_err(&client->dev, "%s(): Failed in reading 0x%02x",
__func__, smb345_HRD_SFT_TEMP);
goto error;
}
setting = retval & HOT_LIMIT_MASK;
if (setting != 0x01) {
setting = retval & (~HOT_LIMIT_MASK);
setting |= 0x01;
SMB_NOTICE("Set HRD SFT limit, retval=%x setting=%x\n", retval, setting);
ret = smb345_write(client, smb345_HRD_SFT_TEMP, setting);
if (ret < 0) {
dev_err(&client->dev, "%s(): Failed in writing 0x%02x to register"
"0x%02x\n", __func__, setting, smb345_HRD_SFT_TEMP);
goto error;
}
} else
SMB_NOTICE("Bypass set HRD SFT limit=%x\n", retval);
ret = smb345_volatile_writes(client, smb345_DISABLE_WRITE);
if (ret < 0) {
dev_err(&client->dev, "%s() charger enable write error..\n", __func__);
goto error;
}
error:
return ret;
}
int smb345_config_thermal_limit(void)
{
struct i2c_client *client = charger->client;
int ret = 0, retval, setting = 0;
ret = smb345_volatile_writes(client, smb345_ENABLE_WRITE);
if (ret < 0) {
dev_err(&client->dev, "%s() charger enable write error..\n", __func__);
goto error;
}
retval = smb345_read(client, smb345_HRD_SFT_TEMP);
if (retval < 0) {
dev_err(&client->dev, "%s(): Failed in reading 0x%02x",
__func__, smb345_HRD_SFT_TEMP);
goto error;
}
setting = retval & HOT_LIMIT_MASK;
if (setting != 0x33) {
setting = retval & (~HOT_LIMIT_MASK);
setting |= 0x33;
SMB_NOTICE("Set HRD SFT limit, retval=%x setting=%x\n", retval, setting);
ret = smb345_write(client, smb345_HRD_SFT_TEMP, setting);
if (ret < 0) {
dev_err(&client->dev, "%s(): Failed in writing 0x%02x to register"
"0x%02x\n", __func__, setting, smb345_HRD_SFT_TEMP);
goto error;
}
} else
SMB_NOTICE("Bypass set HRD SFT limit=%x\n", retval);
ret = smb345_volatile_writes(client, smb345_DISABLE_WRITE);
if (ret < 0) {
dev_err(&client->dev, "%s() charger enable write error..\n", __func__);
goto error;
}
error:
return ret;
}
int smb345_config_thermal_charging(int temp, int volt, int rule)
{
struct i2c_client *client = charger->client;
int ret = 0, retval, setting = 0;
int BAT_Mid_Temp = BAT_Mid_Temp_Wired;
if (rule == THERMAL_RULE1)
BAT_Mid_Temp = BAT_Mid_Temp_Wired;
else if (rule == THERMAL_RULE2)
BAT_Mid_Temp = BAT_Mid_Temp_Wireless;
mdelay(100);
smb345_config_thermal_limit();
SMB_NOTICE("temp=%d, volt=%d\n", temp, volt);
ret = smb345_volatile_writes(client, smb345_ENABLE_WRITE);
if (ret < 0) {
dev_err(&client->dev, "%s() charger enable write error..\n", __func__);
goto error;
}
/*control float voltage*/
retval = smb345_read(client, smb345_FLOAT_VLTG);
if (retval < 0) {
dev_err(&client->dev, "%s(): Failed in reading 0x%02x",
__func__, smb345_FLOAT_VLTG);
goto error;
}
setting = retval & FLOAT_VOLT_MASK;
if (temp <= BAT_Mid_Temp
|| (temp > BAT_Mid_Temp && volt > FLOAT_VOLT_LOW_DECIMAL)
|| temp > BAT_Hot_Limit) {
if (setting != FLOAT_VOLT_43V) {
setting = retval & (~FLOAT_VOLT_MASK);
setting |= FLOAT_VOLT_43V;
SMB_NOTICE("Set Float Volt, retval=%x setting=%x\n", retval, setting);
ret = smb345_write(client, smb345_FLOAT_VLTG, setting);
if (ret < 0) {
dev_err(&client->dev, "%s(): Failed in writing 0x%02x to register"
"0x%02x\n", __func__, setting, smb345_FLOAT_VLTG);
goto error;
}
} else
SMB_NOTICE("Bypass set Float Volt=%x\n", retval);
} else {
if (setting != FLOAT_VOLT_LOW) {
setting = retval & (~FLOAT_VOLT_MASK);
setting |= FLOAT_VOLT_LOW;
SMB_NOTICE("Set Float Volt, retval=%x setting=%x\n", retval, setting);
ret = smb345_write(client, smb345_FLOAT_VLTG, setting);
if (ret < 0) {
dev_err(&client->dev, "%s(): Failed in writing 0x%02x to register"
"0x%02x\n", __func__, setting, smb345_FLOAT_VLTG);
goto error;
}
} else
SMB_NOTICE("Bypass set Float Volt=%x\n", retval);
}
/*charger enable/disable*/
retval = smb345_read(client, smb345_PIN_CTRL);
if (retval < 0) {
dev_err(&client->dev, "%s(): Failed in reading 0x%02x",
__func__, smb345_PIN_CTRL);
goto error;
}
setting = retval & ENABLE_PIN_CTRL_MASK;
if (temp < BAT_Cold_Limit || temp > BAT_Hot_Limit ||
(temp > BAT_Mid_Temp && volt > FLOAT_VOLT_LOW_DECIMAL)) {
if (setting != 0x40) {
SMB_NOTICE("Charger disable\n");
smb345_charger_enable(false);
} else
SMB_NOTICE("Bypass charger disable\n");
} else {
if (setting != 0x60) {
SMB_NOTICE("Charger enable\n");
smb345_charger_enable(true);
} else {
/*interrupt status*/
retval = smb345_read(client, smb345_INTR_STS_B);
if (retval < 0) {
dev_err(&client->dev, "%s(): Failed in reading 0x%02x",
__func__, smb345_INTR_STS_B);
goto error;
}
if ((retval & BAT_OVER_VOLT_MASK) == 0x40) {
SMB_NOTICE("disable then enable charger to recover bat over-volt\n");
smb345_charger_enable(false);
smb345_charger_enable(true);
} else
SMB_NOTICE("Bypass charger enable\n");
}
}
ret = smb345_volatile_writes(client, smb345_DISABLE_WRITE);
if (ret < 0) {
dev_err(&client->dev, "%s() charger enable write error..\n", __func__);
goto error;
}
error:
return ret;
}
EXPORT_SYMBOL(smb345_config_thermal_charging);
static int __devinit smb345_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
struct smb345_platform_data *platform_data;
int ret;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
return -EIO;
charger = kzalloc(sizeof(*charger), GFP_KERNEL);
if (!charger)
return -ENOMEM;
printk("smb345_probe+\n");
charger->client = client;
charger->dev = &client->dev;
i2c_set_clientdata(client, charger);
smb345_otg_setting(charger->client);
ret = sysfs_create_group(&client->dev.kobj, &smb345_group);
if (ret) {
SMB_ERR("Unable to create the sysfs\n");
goto error;
}
mutex_init(&charger->apsd_lock);
mutex_init(&charger->usb_lock);
mutex_init(&charger->pinctrl_lock);
smb345_wq = create_singlethread_workqueue("smb345_wq");
INIT_DELAYED_WORK_DEFERRABLE(&charger->wireless_isr_work,
wireless_isr_work_function);
INIT_DELAYED_WORK_DEFERRABLE(&charger->wireless_det_work,
wireless_det_work_function);
INIT_DELAYED_WORK_DEFERRABLE(&charger->wireless_set_current_work,
wireless_set_current_function);
wake_lock_init(&charger_wakelock, WAKE_LOCK_SUSPEND,
"charger_configuration");
wake_lock_init(&wireless_wakelock, WAKE_LOCK_SUSPEND,
"wireless_charger");
charger->curr_limit = UINT_MAX;
charger->wpc_curr_limit = 300;
charger->wpc_curr_limit_count = 0;
charger->cur_cable_type = non_cable;
charger->old_cable_type = non_cable;
charger->wpc_pok_gpio = -1;
charger->wpc_en1 = -1;
charger->wpc_en2 = -1;
wireless_on = false;
otg_on = false;
wpc_en = false;
disable_DCIN = false;
ret = smb345_inok_irq(charger);
if (ret) {
SMB_ERR("Failed in requesting ACOK# pin isr\n");
goto error;
}
if (client->dev.platform_data) {
platform_data = client->dev.platform_data;
if (platform_data->wpc_pok_gpio != -1) {
charger->wpc_pok_gpio = platform_data->wpc_pok_gpio;
ret = smb345_wireless_irq(charger);
if (ret) {
SMB_ERR("Failed in requesting WPC_POK# pin isr\n");
goto error;
}
}
if (platform_data->wpc_en1 != -1 && platform_data->wpc_en2 != -1) {
charger->wpc_en1 = platform_data->wpc_en1;
charger->wpc_en2 = platform_data->wpc_en2;
wpc_en = true;
ret = smb345_wireless_en_config(charger);
if (ret) {
SMB_ERR("Failed in config WPC en gpio\n");
goto error;
}
}
queue_delayed_work(smb345_wq, &charger->wireless_det_work, WPC_INIT_DET_INTERVAL);
}
printk("smb345_probe-\n");
return 0;
error:
kfree(charger);
return ret;
}
static int __devexit smb345_remove(struct i2c_client *client)
{
struct smb345_charger *charger = i2c_get_clientdata(client);
kfree(charger);
return 0;
}
static int smb345_suspend(struct i2c_client *client, pm_message_t mesg)
{
printk("smb345_suspend+\n");
flush_workqueue(smb345_wq);
if (ac_on || wireless_on || usb_on)
smb345_config_thermal_suspend();
printk("smb345_suspend-\n");
return 0;
}
static int smb345_resume(struct i2c_client *client)
{
printk("smb345_resume+\n");
if (wireless_on != !(gpio_get_value(charger->wpc_pok_gpio)))
wake_lock_timeout(&charger_wakelock, 2*HZ);
printk("smb345_resume-\n");
return 0;
}
void smb345_shutdown(struct i2c_client *client)
{
printk("smb345_shutdown+\n");
if (ac_on || wireless_on || usb_on)
smb345_config_thermal_suspend();
printk("smb345_shutdown-\n");
}
static const struct i2c_device_id smb345_id[] = {
{ "smb345", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, smb345_id);
static struct i2c_driver smb345_i2c_driver = {
.driver = {
.name = "smb345",
},
.probe = smb345_probe,
.remove = __devexit_p(smb345_remove),
.shutdown = smb345_shutdown,
.suspend = smb345_suspend,
.resume = smb345_resume,
.id_table = smb345_id,
};
static int __init smb345_init(void)
{
return i2c_add_driver(&smb345_i2c_driver);
}
module_init(smb345_init);
static void __exit smb345_exit(void)
{
i2c_del_driver(&smb345_i2c_driver);
}
module_exit(smb345_exit);
MODULE_DESCRIPTION("smb345 Battery-Charger");
MODULE_LICENSE("GPL");