864 lines
22 KiB
C
864 lines
22 KiB
C
|
/* Copyright (c) 2012-2013 The Linux Foundation. All rights reserved.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License version 2 and
|
||
|
* only 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.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#define pr_fmt(fmt) "%s: " fmt, __func__
|
||
|
|
||
|
#include <linux/i2c.h>
|
||
|
#include <linux/gpio.h>
|
||
|
#include <linux/errno.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/debugfs.h>
|
||
|
#include <linux/workqueue.h>
|
||
|
#include <linux/interrupt.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/power_supply.h>
|
||
|
#include <linux/i2c/smb350.h>
|
||
|
#include <linux/bitops.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/of_device.h>
|
||
|
#include <linux/of_gpio.h>
|
||
|
#include <linux/printk.h>
|
||
|
|
||
|
/* Register definitions */
|
||
|
#define CHG_CURRENT_REG 0x00 /* Non-Volatile + mirror */
|
||
|
#define CHG_OTHER_CURRENT_REG 0x01 /* Non-Volatile + mirror */
|
||
|
#define VAR_FUNC_REG 0x02 /* Non-Volatile + mirror */
|
||
|
#define FLOAT_VOLTAGE_REG 0x03 /* Non-Volatile + mirror */
|
||
|
#define CHG_CTRL_REG 0x04 /* Non-Volatile + mirror */
|
||
|
#define STAT_TIMER_REG 0x05 /* Non-Volatile + mirror */
|
||
|
#define PIN_ENABLE_CTRL_REG 0x06 /* Non-Volatile + mirror */
|
||
|
#define THERM_CTRL_A_REG 0x07 /* Non-Volatile + mirror */
|
||
|
#define SYSOK_USB3_SELECT_REG 0x08 /* Non-Volatile + mirror */
|
||
|
#define CTRL_FUNCTIONS_REG 0x09 /* Non-Volatile + mirror */
|
||
|
#define OTG_TLIM_THERM_CNTRL_REG 0x0A /* Non-Volatile + mirror */
|
||
|
#define TEMP_MONITOR_REG 0x0B /* Non-Volatile + mirror */
|
||
|
#define FAULT_IRQ_REG 0x0C /* Non-Volatile */
|
||
|
#define IRQ_ENABLE_REG 0x0D /* Non-Volatile */
|
||
|
#define SYSOK_REG 0x0E /* Non-Volatile + mirror */
|
||
|
|
||
|
#define AUTO_INPUT_VOLT_DETECT_REG 0x10 /* Non-Volatile Read-Only */
|
||
|
#define STATUS_IRQ_REG 0x11 /* Non-Volatile Read-Only */
|
||
|
#define I2C_SLAVE_ADDR_REG 0x12 /* Non-Volatile Read-Only */
|
||
|
|
||
|
#define CMD_A_REG 0x30 /* Volatile Read-Write */
|
||
|
#define CMD_B_REG 0x31 /* Volatile Read-Write */
|
||
|
#define CMD_C_REG 0x33 /* Volatile Read-Write */
|
||
|
|
||
|
#define HW_VERSION_REG 0x34 /* Volatile Read-Only */
|
||
|
|
||
|
#define IRQ_STATUS_A_REG 0x35 /* Volatile Read-Only */
|
||
|
#define IRQ_STATUS_B_REG 0x36 /* Volatile Read-Only */
|
||
|
#define IRQ_STATUS_C_REG 0x37 /* Volatile Read-Only */
|
||
|
#define IRQ_STATUS_D_REG 0x38 /* Volatile Read-Only */
|
||
|
#define IRQ_STATUS_E_REG 0x39 /* Volatile Read-Only */
|
||
|
#define IRQ_STATUS_F_REG 0x3A /* Volatile Read-Only */
|
||
|
|
||
|
#define STATUS_A_REG 0x3B /* Volatile Read-Only */
|
||
|
#define STATUS_B_REG 0x3D /* Volatile Read-Only */
|
||
|
/* Note: STATUS_C_REG was removed from SMB349 to SMB350 */
|
||
|
#define STATUS_D_REG 0x3E /* Volatile Read-Only */
|
||
|
#define STATUS_E_REG 0x3F /* Volatile Read-Only */
|
||
|
|
||
|
#define IRQ_STATUS_NUM (IRQ_STATUS_F_REG - IRQ_STATUS_A_REG + 1)
|
||
|
|
||
|
/* Status bits and masks */
|
||
|
#define SMB350_MASK(BITS, POS) ((u8)(((1 << BITS) - 1) << POS))
|
||
|
#define FAST_CHG_CURRENT_MASK SMB350_MASK(4, 4)
|
||
|
|
||
|
#define SMB350_FAST_CHG_MIN_MA 1000
|
||
|
#define SMB350_FAST_CHG_STEP_MA 200
|
||
|
#define SMB350_FAST_CHG_MAX_MA 3600
|
||
|
|
||
|
#define TERM_CURRENT_MASK SMB350_MASK(3, 2)
|
||
|
|
||
|
#define SMB350_TERM_CUR_MIN_MA 200
|
||
|
#define SMB350_TERM_CUR_STEP_MA 100
|
||
|
#define SMB350_TERM_CUR_MAX_MA 700
|
||
|
|
||
|
#define CMD_A_VOLATILE_WR_PERM BIT(7)
|
||
|
#define CHG_CTRL_CURR_TERM_END_CHG BIT(6)
|
||
|
|
||
|
enum smb350_chg_status {
|
||
|
SMB_CHG_STATUS_NONE = 0,
|
||
|
SMB_CHG_STATUS_PRE_CHARGE = 1,
|
||
|
SMB_CHG_STATUS_FAST_CHARGE = 2,
|
||
|
SMB_CHG_STATUS_TAPER_CHARGE = 3,
|
||
|
};
|
||
|
|
||
|
static const char * const smb350_chg_status[] = {
|
||
|
"none",
|
||
|
"pre-charge",
|
||
|
"fast-charge",
|
||
|
"taper-charge"
|
||
|
};
|
||
|
|
||
|
struct smb350_device {
|
||
|
/* setup */
|
||
|
int chg_current_ma;
|
||
|
int term_current_ma;
|
||
|
int chg_en_n_gpio;
|
||
|
int chg_susp_n_gpio;
|
||
|
int stat_gpio;
|
||
|
int irq;
|
||
|
/* internal */
|
||
|
enum smb350_chg_status chg_status;
|
||
|
struct i2c_client *client;
|
||
|
struct delayed_work irq_work;
|
||
|
struct dentry *dent;
|
||
|
struct wake_lock chg_wake_lock;
|
||
|
struct power_supply dc_psy;
|
||
|
};
|
||
|
|
||
|
static struct smb350_device *smb350_dev;
|
||
|
|
||
|
struct debug_reg {
|
||
|
char *name;
|
||
|
u8 reg;
|
||
|
};
|
||
|
|
||
|
#define SMB350_DEBUG_REG(x) {#x, x##_REG}
|
||
|
|
||
|
static struct debug_reg smb350_debug_regs[] = {
|
||
|
SMB350_DEBUG_REG(CHG_CURRENT),
|
||
|
SMB350_DEBUG_REG(CHG_OTHER_CURRENT),
|
||
|
SMB350_DEBUG_REG(VAR_FUNC),
|
||
|
SMB350_DEBUG_REG(FLOAT_VOLTAGE),
|
||
|
SMB350_DEBUG_REG(CHG_CTRL),
|
||
|
SMB350_DEBUG_REG(STAT_TIMER),
|
||
|
SMB350_DEBUG_REG(PIN_ENABLE_CTRL),
|
||
|
SMB350_DEBUG_REG(THERM_CTRL_A),
|
||
|
SMB350_DEBUG_REG(SYSOK_USB3_SELECT),
|
||
|
SMB350_DEBUG_REG(CTRL_FUNCTIONS),
|
||
|
SMB350_DEBUG_REG(OTG_TLIM_THERM_CNTRL),
|
||
|
SMB350_DEBUG_REG(TEMP_MONITOR),
|
||
|
SMB350_DEBUG_REG(FAULT_IRQ),
|
||
|
SMB350_DEBUG_REG(IRQ_ENABLE),
|
||
|
SMB350_DEBUG_REG(SYSOK),
|
||
|
SMB350_DEBUG_REG(AUTO_INPUT_VOLT_DETECT),
|
||
|
SMB350_DEBUG_REG(STATUS_IRQ),
|
||
|
SMB350_DEBUG_REG(I2C_SLAVE_ADDR),
|
||
|
SMB350_DEBUG_REG(CMD_A),
|
||
|
SMB350_DEBUG_REG(CMD_B),
|
||
|
SMB350_DEBUG_REG(CMD_C),
|
||
|
SMB350_DEBUG_REG(HW_VERSION),
|
||
|
SMB350_DEBUG_REG(IRQ_STATUS_A),
|
||
|
SMB350_DEBUG_REG(IRQ_STATUS_B),
|
||
|
SMB350_DEBUG_REG(IRQ_STATUS_C),
|
||
|
SMB350_DEBUG_REG(IRQ_STATUS_D),
|
||
|
SMB350_DEBUG_REG(IRQ_STATUS_E),
|
||
|
SMB350_DEBUG_REG(IRQ_STATUS_F),
|
||
|
SMB350_DEBUG_REG(STATUS_A),
|
||
|
SMB350_DEBUG_REG(STATUS_B),
|
||
|
SMB350_DEBUG_REG(STATUS_D),
|
||
|
SMB350_DEBUG_REG(STATUS_E),
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* Read 8-bit register value. return negative value on error.
|
||
|
*/
|
||
|
static int smb350_read_reg(struct i2c_client *client, u8 reg)
|
||
|
{
|
||
|
int val;
|
||
|
|
||
|
val = i2c_smbus_read_byte_data(client, reg);
|
||
|
if (val < 0)
|
||
|
pr_err("i2c read fail. reg=0x%x.ret=%d.\n", reg, val);
|
||
|
else
|
||
|
pr_debug("reg=0x%02X.val=0x%02X.\n", reg , val);
|
||
|
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Write 8-bit register value. return negative value on error.
|
||
|
*/
|
||
|
static int smb350_write_reg(struct i2c_client *client, u8 reg, u8 val)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
ret = i2c_smbus_write_byte_data(client, reg, val);
|
||
|
if (ret < 0)
|
||
|
pr_err("i2c read fail. reg=0x%x.val=0x%x.ret=%d.\n",
|
||
|
reg, val, ret);
|
||
|
else
|
||
|
pr_debug("reg=0x%02X.val=0x%02X.\n", reg , val);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int smb350_masked_write(struct i2c_client *client, int reg, u8 mask,
|
||
|
u8 val)
|
||
|
{
|
||
|
int ret;
|
||
|
int temp;
|
||
|
int shift = find_first_bit((unsigned long *) &mask, 8);
|
||
|
|
||
|
temp = smb350_read_reg(client, reg);
|
||
|
if (temp < 0)
|
||
|
return temp;
|
||
|
|
||
|
temp &= ~mask;
|
||
|
temp |= (val << shift) & mask;
|
||
|
ret = smb350_write_reg(client, reg, temp);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static bool smb350_is_dc_present(struct i2c_client *client)
|
||
|
{
|
||
|
u16 irq_status_f = smb350_read_reg(client, IRQ_STATUS_F_REG);
|
||
|
bool power_ok = irq_status_f & 0x01;
|
||
|
|
||
|
/* Power-ok , IRQ_STATUS_F_REG bit#0 */
|
||
|
if (power_ok)
|
||
|
pr_debug("DC is present.\n");
|
||
|
else
|
||
|
pr_debug("DC is missing.\n");
|
||
|
|
||
|
return power_ok;
|
||
|
}
|
||
|
|
||
|
static bool smb350_is_charger_present(struct i2c_client *client)
|
||
|
{
|
||
|
int val;
|
||
|
|
||
|
/* Normally the device is non-removable and embedded on the board.
|
||
|
* Verify that charger is present by getting I2C response.
|
||
|
*/
|
||
|
val = smb350_read_reg(client, STATUS_B_REG);
|
||
|
if (val < 0)
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static int smb350_get_prop_charge_type(struct smb350_device *dev)
|
||
|
{
|
||
|
int status_b;
|
||
|
enum smb350_chg_status status;
|
||
|
int chg_type = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
|
||
|
bool chg_enabled;
|
||
|
bool charger_err;
|
||
|
struct i2c_client *client = dev->client;
|
||
|
|
||
|
status_b = smb350_read_reg(client, STATUS_B_REG);
|
||
|
if (status_b < 0) {
|
||
|
pr_err("failed to read STATUS_B_REG.\n");
|
||
|
return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
|
||
|
}
|
||
|
|
||
|
chg_enabled = (bool) (status_b & 0x01);
|
||
|
charger_err = (bool) (status_b & (1<<6));
|
||
|
|
||
|
if (!chg_enabled) {
|
||
|
pr_warn("Charging not enabled.\n");
|
||
|
/* release the wake-lock when DC power removed */
|
||
|
if (wake_lock_active(&dev->chg_wake_lock))
|
||
|
wake_unlock(&dev->chg_wake_lock);
|
||
|
return POWER_SUPPLY_CHARGE_TYPE_NONE;
|
||
|
}
|
||
|
|
||
|
if (charger_err) {
|
||
|
pr_warn("Charger error detected.\n");
|
||
|
return POWER_SUPPLY_CHARGE_TYPE_NONE;
|
||
|
}
|
||
|
|
||
|
status = (status_b >> 1) & 0x3;
|
||
|
|
||
|
if (status == SMB_CHG_STATUS_NONE)
|
||
|
chg_type = POWER_SUPPLY_CHARGE_TYPE_NONE;
|
||
|
else if (status == SMB_CHG_STATUS_FAST_CHARGE) /* constant current */
|
||
|
chg_type = POWER_SUPPLY_CHARGE_TYPE_FAST;
|
||
|
else if (status == SMB_CHG_STATUS_TAPER_CHARGE) /* constant voltage */
|
||
|
chg_type = POWER_SUPPLY_CHARGE_TYPE_FAST;
|
||
|
else if (status == SMB_CHG_STATUS_PRE_CHARGE)
|
||
|
chg_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
|
||
|
|
||
|
pr_debug("smb-chg-status=%d=%s.\n", status, smb350_chg_status[status]);
|
||
|
|
||
|
if (dev->chg_status != status) { /* Status changed */
|
||
|
if (status == SMB_CHG_STATUS_NONE) {
|
||
|
pr_debug("Charging stopped.\n");
|
||
|
wake_unlock(&dev->chg_wake_lock);
|
||
|
} else {
|
||
|
pr_debug("Charging started.\n");
|
||
|
wake_lock(&dev->chg_wake_lock);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
dev->chg_status = status;
|
||
|
|
||
|
return chg_type;
|
||
|
}
|
||
|
|
||
|
static void smb350_enable_charging(struct smb350_device *dev, bool enable)
|
||
|
{
|
||
|
int val = !enable; /* active low */
|
||
|
|
||
|
pr_debug("enable=%d.\n", enable);
|
||
|
|
||
|
gpio_set_value_cansleep(dev->chg_en_n_gpio, val);
|
||
|
}
|
||
|
|
||
|
/* When the status bit of a certain condition is read,
|
||
|
* the corresponding IRQ signal is cleared.
|
||
|
*/
|
||
|
static int smb350_clear_irq(struct i2c_client *client)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
ret = smb350_read_reg(client, IRQ_STATUS_A_REG);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
ret = smb350_read_reg(client, IRQ_STATUS_B_REG);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
ret = smb350_read_reg(client, IRQ_STATUS_C_REG);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
ret = smb350_read_reg(client, IRQ_STATUS_D_REG);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
ret = smb350_read_reg(client, IRQ_STATUS_E_REG);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
ret = smb350_read_reg(client, IRQ_STATUS_F_REG);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Do the IRQ work from a thread context rather than interrupt context.
|
||
|
* Read status registers to clear interrupt source.
|
||
|
* Notify the power-supply driver about change detected.
|
||
|
* Relevant events for start/stop charging:
|
||
|
* 1. DC insert/remove
|
||
|
* 2. End-Of-Charging
|
||
|
* 3. Battery insert/remove
|
||
|
* 4. Temperture too hot/cold
|
||
|
* 5. Charging timeout expired.
|
||
|
*/
|
||
|
static void smb350_irq_worker(struct work_struct *work)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct smb350_device *dev =
|
||
|
container_of(work, struct smb350_device, irq_work.work);
|
||
|
|
||
|
ret = smb350_clear_irq(dev->client);
|
||
|
if (ret == 0) { /* Cleared ok */
|
||
|
/* Notify Battery-psy about status changed */
|
||
|
pr_debug("Notify power_supply_changed.\n");
|
||
|
power_supply_changed(&dev->dc_psy);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* The STAT pin is low when charging and high when not charging.
|
||
|
* When the smb350 start/stop charging the STAT pin triggers an interrupt.
|
||
|
* Interrupt is triggered on both rising or falling edge.
|
||
|
*/
|
||
|
static irqreturn_t smb350_irq(int irq, void *dev_id)
|
||
|
{
|
||
|
struct smb350_device *dev = dev_id;
|
||
|
|
||
|
pr_debug("\n");
|
||
|
|
||
|
/* I2C transfers API should not run in interrupt context */
|
||
|
schedule_delayed_work(&dev->irq_work, msecs_to_jiffies(100));
|
||
|
|
||
|
return IRQ_HANDLED;
|
||
|
}
|
||
|
|
||
|
static enum power_supply_property pm_power_props[] = {
|
||
|
/* real time */
|
||
|
POWER_SUPPLY_PROP_PRESENT,
|
||
|
POWER_SUPPLY_PROP_ONLINE,
|
||
|
POWER_SUPPLY_PROP_CHARGE_TYPE,
|
||
|
/* fixed */
|
||
|
POWER_SUPPLY_PROP_MANUFACTURER,
|
||
|
POWER_SUPPLY_PROP_MODEL_NAME,
|
||
|
POWER_SUPPLY_PROP_CURRENT_MAX,
|
||
|
};
|
||
|
|
||
|
static char *pm_power_supplied_to[] = {
|
||
|
"battery",
|
||
|
};
|
||
|
|
||
|
static int smb350_get_property(struct power_supply *psy,
|
||
|
enum power_supply_property psp,
|
||
|
union power_supply_propval *val)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct smb350_device *dev = container_of(psy,
|
||
|
struct smb350_device,
|
||
|
dc_psy);
|
||
|
struct i2c_client *client = dev->client;
|
||
|
|
||
|
switch (psp) {
|
||
|
case POWER_SUPPLY_PROP_PRESENT:
|
||
|
val->intval = smb350_is_charger_present(client);
|
||
|
break;
|
||
|
case POWER_SUPPLY_PROP_ONLINE:
|
||
|
val->intval = smb350_is_dc_present(client);
|
||
|
break;
|
||
|
case POWER_SUPPLY_PROP_CHARGE_TYPE:
|
||
|
val->intval = smb350_get_prop_charge_type(dev);
|
||
|
break;
|
||
|
case POWER_SUPPLY_PROP_MODEL_NAME:
|
||
|
val->strval = SMB350_NAME;
|
||
|
break;
|
||
|
case POWER_SUPPLY_PROP_MANUFACTURER:
|
||
|
val->strval = "Summit Microelectronics";
|
||
|
break;
|
||
|
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
||
|
val->intval = dev->chg_current_ma;
|
||
|
break;
|
||
|
default:
|
||
|
pr_err("Invalid prop = %d.\n", psp);
|
||
|
ret = -EINVAL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int smb350_set_property(struct power_supply *psy,
|
||
|
enum power_supply_property psp,
|
||
|
const union power_supply_propval *val)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct smb350_device *dev =
|
||
|
container_of(psy, struct smb350_device, dc_psy);
|
||
|
|
||
|
switch (psp) {
|
||
|
/*
|
||
|
* Allow a smart battery to Start/Stop charging.
|
||
|
* i.e. when End-Of-Charging detected.
|
||
|
* The SMB350 can be configured to terminate charging
|
||
|
* when charge-current reaching Termination-Current.
|
||
|
*/
|
||
|
case POWER_SUPPLY_PROP_ONLINE:
|
||
|
smb350_enable_charging(dev, val->intval);
|
||
|
break;
|
||
|
default:
|
||
|
pr_err("Invalid prop = %d.\n", psp);
|
||
|
ret = -EINVAL;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int smb350_set_chg_current(struct i2c_client *client, int current_ma)
|
||
|
{
|
||
|
int ret;
|
||
|
u8 temp;
|
||
|
|
||
|
if ((current_ma < SMB350_FAST_CHG_MIN_MA) ||
|
||
|
(current_ma > SMB350_FAST_CHG_MAX_MA)) {
|
||
|
pr_err("invalid current %d mA.\n", current_ma);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
temp = (current_ma - SMB350_FAST_CHG_MIN_MA) / SMB350_FAST_CHG_STEP_MA;
|
||
|
|
||
|
pr_debug("fast-chg-current=%d mA setting %02x\n", current_ma, temp);
|
||
|
|
||
|
ret = smb350_masked_write(client, CHG_CURRENT_REG,
|
||
|
FAST_CHG_CURRENT_MASK, temp);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int smb350_set_term_current(struct i2c_client *client, int current_ma)
|
||
|
{
|
||
|
int ret;
|
||
|
u8 temp;
|
||
|
|
||
|
if ((current_ma < SMB350_TERM_CUR_MIN_MA) ||
|
||
|
(current_ma > SMB350_TERM_CUR_MAX_MA)) {
|
||
|
pr_err("invalid current %d mA to set\n", current_ma);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
temp = (current_ma - SMB350_TERM_CUR_MIN_MA) / SMB350_TERM_CUR_STEP_MA;
|
||
|
|
||
|
pr_debug("term-current=%d mA setting %02x\n", current_ma, temp);
|
||
|
|
||
|
ret = smb350_masked_write(client, CHG_OTHER_CURRENT_REG,
|
||
|
TERM_CURRENT_MASK, temp);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int smb350_set_reg(void *data, u64 val)
|
||
|
{
|
||
|
u32 addr = (u32) data;
|
||
|
int ret;
|
||
|
struct i2c_client *client = smb350_dev->client;
|
||
|
|
||
|
ret = smb350_write_reg(client, addr, (u8) val);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int smb350_get_reg(void *data, u64 *val)
|
||
|
{
|
||
|
u32 addr = (u32) data;
|
||
|
int ret;
|
||
|
struct i2c_client *client = smb350_dev->client;
|
||
|
|
||
|
ret = smb350_read_reg(client, addr);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
|
||
|
*val = ret;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
DEFINE_SIMPLE_ATTRIBUTE(reg_fops, smb350_get_reg, smb350_set_reg, "0x%02llx\n");
|
||
|
|
||
|
static int smb350_create_debugfs_entries(struct smb350_device *dev)
|
||
|
{
|
||
|
int i;
|
||
|
dev->dent = debugfs_create_dir(SMB350_NAME, NULL);
|
||
|
if (IS_ERR(dev->dent)) {
|
||
|
pr_err("smb350 driver couldn't create debugfs dir\n");
|
||
|
return -EFAULT;
|
||
|
}
|
||
|
|
||
|
for (i = 0 ; i < ARRAY_SIZE(smb350_debug_regs) ; i++) {
|
||
|
char *name = smb350_debug_regs[i].name;
|
||
|
u32 reg = smb350_debug_regs[i].reg;
|
||
|
struct dentry *file;
|
||
|
|
||
|
file = debugfs_create_file(name, 0644, dev->dent,
|
||
|
(void *) reg, ®_fops);
|
||
|
if (IS_ERR(file)) {
|
||
|
pr_err("debugfs_create_file %s failed.\n", name);
|
||
|
return -EFAULT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int smb350_set_volatile_params(struct smb350_device *dev)
|
||
|
{
|
||
|
int ret;
|
||
|
struct i2c_client *client = dev->client;
|
||
|
|
||
|
pr_debug("\n");
|
||
|
|
||
|
ret = smb350_write_reg(client, CMD_A_REG, CMD_A_VOLATILE_WR_PERM);
|
||
|
if (ret) {
|
||
|
pr_err("Failed to set VOLATILE_WR_PERM ret=%d\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* Disable SMB350 pulse-IRQ mechanism,
|
||
|
* we use interrupts based on charging-status-transition
|
||
|
*/
|
||
|
/* Enable STATUS output (regardless of IRQ-pulses) */
|
||
|
smb350_masked_write(client, CMD_A_REG, BIT(0), 0);
|
||
|
|
||
|
/* Disable LED blinking - avoid periodic irq */
|
||
|
smb350_masked_write(client, PIN_ENABLE_CTRL_REG, BIT(7), 0);
|
||
|
|
||
|
/* Disable Failure SMB-IRQ */
|
||
|
ret = smb350_write_reg(client, FAULT_IRQ_REG, 0x00);
|
||
|
if (ret) {
|
||
|
pr_err("Failed to set FAULT_IRQ_REG ret=%d\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* Disable Event IRQ */
|
||
|
ret = smb350_write_reg(client, IRQ_ENABLE_REG, 0x00);
|
||
|
if (ret) {
|
||
|
pr_err("Failed to set IRQ_ENABLE_REG ret=%d\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* Enable charging/not-charging status output via STAT pin */
|
||
|
smb350_masked_write(client, STAT_TIMER_REG, BIT(5), 0);
|
||
|
|
||
|
/* Disable Automatic Recharge */
|
||
|
smb350_masked_write(client, CHG_CTRL_REG, BIT(7), 1);
|
||
|
|
||
|
/* Set fast-charge current */
|
||
|
ret = smb350_set_chg_current(client, dev->chg_current_ma);
|
||
|
if (ret) {
|
||
|
pr_err("Failed to set FAST_CHG_CURRENT ret=%d\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
if (dev->term_current_ma > 0) {
|
||
|
/* Enable Current Termination */
|
||
|
smb350_masked_write(client, CHG_CTRL_REG, BIT(6), 0);
|
||
|
|
||
|
/* Set Termination current */
|
||
|
smb350_set_term_current(client, dev->term_current_ma);
|
||
|
} else {
|
||
|
/* Disable Current Termination */
|
||
|
smb350_masked_write(client, CHG_CTRL_REG, BIT(6), 1);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int __devinit smb350_register_psy(struct smb350_device *dev)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
dev->dc_psy.name = "dc";
|
||
|
dev->dc_psy.type = POWER_SUPPLY_TYPE_MAINS;
|
||
|
dev->dc_psy.supplied_to = pm_power_supplied_to;
|
||
|
dev->dc_psy.num_supplicants = ARRAY_SIZE(pm_power_supplied_to);
|
||
|
dev->dc_psy.properties = pm_power_props;
|
||
|
dev->dc_psy.num_properties = ARRAY_SIZE(pm_power_props);
|
||
|
dev->dc_psy.get_property = smb350_get_property;
|
||
|
dev->dc_psy.set_property = smb350_set_property;
|
||
|
|
||
|
ret = power_supply_register(&dev->client->dev,
|
||
|
&dev->dc_psy);
|
||
|
if (ret) {
|
||
|
pr_err("failed to register power_supply. ret=%d.\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int __devinit smb350_probe(struct i2c_client *client,
|
||
|
const struct i2c_device_id *id)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
const struct smb350_platform_data *pdata;
|
||
|
struct device_node *dev_node = client->dev.of_node;
|
||
|
struct smb350_device *dev;
|
||
|
u8 version;
|
||
|
|
||
|
/* STAT pin change on start/stop charging */
|
||
|
u32 irq_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
|
||
|
|
||
|
if (!i2c_check_functionality(client->adapter,
|
||
|
I2C_FUNC_SMBUS_BYTE_DATA)) {
|
||
|
pr_err("i2c func fail.\n");
|
||
|
return -EIO;
|
||
|
}
|
||
|
|
||
|
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
||
|
if (!dev) {
|
||
|
pr_err("alloc fail.\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
smb350_dev = dev;
|
||
|
dev->client = client;
|
||
|
|
||
|
if (dev_node) {
|
||
|
dev->chg_en_n_gpio =
|
||
|
of_get_named_gpio(dev_node, "summit,chg-en-n-gpio", 0);
|
||
|
pr_debug("chg_en_n_gpio = %d.\n", dev->chg_en_n_gpio);
|
||
|
|
||
|
dev->chg_susp_n_gpio =
|
||
|
of_get_named_gpio(dev_node,
|
||
|
"summit,chg-susp-n-gpio", 0);
|
||
|
pr_debug("chg_susp_n_gpio = %d.\n", dev->chg_susp_n_gpio);
|
||
|
|
||
|
dev->stat_gpio =
|
||
|
of_get_named_gpio(dev_node, "summit,stat-gpio", 0);
|
||
|
pr_debug("stat_gpio = %d.\n", dev->stat_gpio);
|
||
|
|
||
|
ret = of_property_read_u32(dev_node, "summit,chg-current-ma",
|
||
|
&(dev->chg_current_ma));
|
||
|
pr_debug("chg_current_ma = %d.\n", dev->chg_current_ma);
|
||
|
if (ret) {
|
||
|
pr_err("Unable to read chg_current.\n");
|
||
|
return ret;
|
||
|
}
|
||
|
ret = of_property_read_u32(dev_node, "summit,term-current-ma",
|
||
|
&(dev->term_current_ma));
|
||
|
pr_debug("term_current_ma = %d.\n", dev->term_current_ma);
|
||
|
if (ret) {
|
||
|
pr_err("Unable to read term_current_ma.\n");
|
||
|
return ret;
|
||
|
}
|
||
|
} else {
|
||
|
pdata = client->dev.platform_data;
|
||
|
|
||
|
if (pdata == NULL) {
|
||
|
pr_err("no platform data.\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
dev->chg_en_n_gpio = pdata->chg_en_n_gpio;
|
||
|
dev->chg_susp_n_gpio = pdata->chg_susp_n_gpio;
|
||
|
dev->stat_gpio = pdata->stat_gpio;
|
||
|
|
||
|
dev->chg_current_ma = pdata->chg_current_ma;
|
||
|
dev->term_current_ma = pdata->term_current_ma;
|
||
|
}
|
||
|
|
||
|
ret = gpio_request(dev->stat_gpio, "smb350_stat");
|
||
|
if (ret) {
|
||
|
pr_err("gpio_request failed for %d ret=%d\n",
|
||
|
dev->stat_gpio, ret);
|
||
|
goto err_stat_gpio;
|
||
|
}
|
||
|
dev->irq = gpio_to_irq(dev->stat_gpio);
|
||
|
pr_debug("irq#=%d.\n", dev->irq);
|
||
|
|
||
|
ret = gpio_request(dev->chg_susp_n_gpio, "smb350_suspend");
|
||
|
if (ret) {
|
||
|
pr_err("gpio_request failed for %d ret=%d\n",
|
||
|
dev->chg_susp_n_gpio, ret);
|
||
|
goto err_susp_gpio;
|
||
|
}
|
||
|
|
||
|
ret = gpio_request(dev->chg_en_n_gpio, "smb350_charger_enable");
|
||
|
if (ret) {
|
||
|
pr_err("gpio_request failed for %d ret=%d\n",
|
||
|
dev->chg_en_n_gpio, ret);
|
||
|
goto err_en_gpio;
|
||
|
}
|
||
|
|
||
|
i2c_set_clientdata(client, dev);
|
||
|
|
||
|
/* Disable battery charging by default on power up.
|
||
|
* Battery charging is enabled by BMS or Battery-Gauge
|
||
|
* by using the set_property callback.
|
||
|
*/
|
||
|
smb350_enable_charging(dev, false);
|
||
|
msleep(100);
|
||
|
gpio_set_value_cansleep(dev->chg_susp_n_gpio, 1); /* Normal */
|
||
|
msleep(100); /* Allow the device to exist shutdown */
|
||
|
|
||
|
/* I2C transaction allowed only after device exit suspend */
|
||
|
ret = smb350_read_reg(client, I2C_SLAVE_ADDR_REG);
|
||
|
if ((ret>>1) != client->addr) {
|
||
|
pr_err("No device.\n");
|
||
|
ret = -ENODEV;
|
||
|
goto err_no_dev;
|
||
|
}
|
||
|
|
||
|
version = smb350_read_reg(client, HW_VERSION_REG);
|
||
|
version &= 0x0F; /* bits 0..3 */
|
||
|
|
||
|
ret = smb350_set_volatile_params(dev);
|
||
|
if (ret)
|
||
|
goto err_set_params;
|
||
|
|
||
|
ret = smb350_register_psy(dev);
|
||
|
if (ret)
|
||
|
goto err_set_params;
|
||
|
|
||
|
ret = smb350_create_debugfs_entries(dev);
|
||
|
if (ret)
|
||
|
goto err_debugfs;
|
||
|
|
||
|
INIT_DELAYED_WORK(&dev->irq_work, smb350_irq_worker);
|
||
|
wake_lock_init(&dev->chg_wake_lock,
|
||
|
WAKE_LOCK_SUSPEND, SMB350_NAME);
|
||
|
|
||
|
ret = request_irq(dev->irq, smb350_irq, irq_flags,
|
||
|
"smb350_irq", dev);
|
||
|
if (ret) {
|
||
|
pr_err("request_irq %d failed.ret=%d\n", dev->irq, ret);
|
||
|
goto err_irq;
|
||
|
}
|
||
|
|
||
|
pr_info("HW Version = 0x%X.\n", version);
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
err_irq:
|
||
|
err_debugfs:
|
||
|
if (dev->dent)
|
||
|
debugfs_remove_recursive(dev->dent);
|
||
|
err_no_dev:
|
||
|
err_set_params:
|
||
|
gpio_free(dev->chg_en_n_gpio);
|
||
|
err_en_gpio:
|
||
|
gpio_free(dev->chg_susp_n_gpio);
|
||
|
err_susp_gpio:
|
||
|
gpio_free(dev->stat_gpio);
|
||
|
err_stat_gpio:
|
||
|
kfree(smb350_dev);
|
||
|
smb350_dev = NULL;
|
||
|
|
||
|
pr_info("FAIL.\n");
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int __devexit smb350_remove(struct i2c_client *client)
|
||
|
{
|
||
|
struct smb350_device *dev = i2c_get_clientdata(client);
|
||
|
|
||
|
power_supply_unregister(&dev->dc_psy);
|
||
|
gpio_free(dev->chg_en_n_gpio);
|
||
|
gpio_free(dev->chg_susp_n_gpio);
|
||
|
if (dev->stat_gpio)
|
||
|
gpio_free(dev->stat_gpio);
|
||
|
if (dev->irq)
|
||
|
free_irq(dev->irq, dev);
|
||
|
if (dev->dent)
|
||
|
debugfs_remove_recursive(dev->dent);
|
||
|
kfree(smb350_dev);
|
||
|
smb350_dev = NULL;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct i2c_device_id smb350_id[] = {
|
||
|
{SMB350_NAME, 0},
|
||
|
{},
|
||
|
};
|
||
|
MODULE_DEVICE_TABLE(i2c, smb350_id);
|
||
|
|
||
|
static const struct of_device_id smb350_match[] = {
|
||
|
{ .compatible = "summit,smb350-charger", },
|
||
|
{ },
|
||
|
};
|
||
|
|
||
|
static struct i2c_driver smb350_driver = {
|
||
|
.driver = {
|
||
|
.name = SMB350_NAME,
|
||
|
.owner = THIS_MODULE,
|
||
|
.of_match_table = of_match_ptr(smb350_match),
|
||
|
},
|
||
|
.probe = smb350_probe,
|
||
|
.remove = __devexit_p(smb350_remove),
|
||
|
.id_table = smb350_id,
|
||
|
};
|
||
|
|
||
|
static int __init smb350_init(void)
|
||
|
{
|
||
|
return i2c_add_driver(&smb350_driver);
|
||
|
}
|
||
|
module_init(smb350_init);
|
||
|
|
||
|
static void __exit smb350_exit(void)
|
||
|
{
|
||
|
return i2c_del_driver(&smb350_driver);
|
||
|
}
|
||
|
module_exit(smb350_exit);
|
||
|
|
||
|
MODULE_DESCRIPTION("Driver for SMB350 charger chip");
|
||
|
MODULE_LICENSE("GPL v2");
|
||
|
MODULE_ALIAS("i2c:" SMB350_NAME);
|