mirror of
https://github.com/followmsi/android_kernel_google_msm.git
synced 2024-11-06 23:17:41 +00:00
40861c769c
Signed-off-by: flar2 <asegaert@gmail.com>
1410 lines
36 KiB
C
1410 lines
36 KiB
C
/*
|
|
* 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);
|
|
static ssize_t smb345_float_voltage_show(struct device *dev, struct device_attribute *attr, char *buf);
|
|
static ssize_t smb345_float_voltage_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count);
|
|
|
|
/* 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;
|
|
static int float_volt_setting = 4300;
|
|
|
|
/* Sysfs interface */
|
|
static DEVICE_ATTR(reg_status, S_IWUSR | S_IRUGO, smb345_reg_show, NULL);
|
|
static DEVICE_ATTR(float_voltage, 0644, smb345_float_voltage_show,
|
|
smb345_float_voltage_store);
|
|
|
|
static struct attribute *smb345_attributes[] = {
|
|
&dev_attr_reg_status.attr,
|
|
&dev_attr_float_voltage.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 ssize_t smb345_float_voltage_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
return sprintf(buf, "%u\n", float_volt_setting);
|
|
}
|
|
|
|
/* Set charger float voltage; desired value in mV. */
|
|
static ssize_t smb345_float_voltage_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
int val = 0;
|
|
|
|
if(kstrtoint(buf, 0, &val) != 0)
|
|
goto error;
|
|
val = (val / 20) * 20; /* the charger can only do 20mV increments */
|
|
if(val < 3800 || val > 4300)
|
|
goto error;
|
|
|
|
float_volt_setting = val;
|
|
|
|
return count;
|
|
error:
|
|
return -EINVAL;
|
|
}
|
|
|
|
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;
|
|
/*Charger float voltage for normal temperature conditions. Default 4.3V.*/
|
|
int flt_volt_43 = FLOAT_VOLT_43V;
|
|
/*Charger float voltage for high temperature conditions. Default 4.1V.*/
|
|
int flt_volt_low = FLOAT_VOLT_LOW;
|
|
|
|
flt_volt_43 = (float_volt_setting - 3500) / 20;
|
|
|
|
if(flt_volt_43 < 0 || flt_volt_43 > FLOAT_VOLT_43V) {
|
|
SMB_NOTICE("BUG: Invalid float voltage setting calculated: %d\n", flt_volt_43);
|
|
flt_volt_43 = FLOAT_VOLT_43V;
|
|
}
|
|
if(flt_volt_low > flt_volt_43) flt_volt_low = flt_volt_43;
|
|
|
|
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 != flt_volt_43) {
|
|
setting = retval & (~FLOAT_VOLT_MASK);
|
|
setting |= flt_volt_43;
|
|
SMB_NOTICE("Set Float Volt, retval=%x setting=%x V=%dmV\n", retval, setting, float_volt_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 setting=%x V=%dmV\n", retval, float_volt_setting);
|
|
} else {
|
|
if (setting != flt_volt_low) {
|
|
setting = retval & (~FLOAT_VOLT_MASK);
|
|
setting |= flt_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");
|