959 lines
24 KiB
C
959 lines
24 KiB
C
/*
|
|
* Silergy SY6974 charger driver
|
|
*
|
|
* Copyright (C) 2015 Intel Corporation
|
|
*
|
|
* 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/i2c.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/types.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/gpio.h>
|
|
|
|
#include <linux/acpi.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_gpio.h>
|
|
|
|
#ifdef CONFIG_ARCH_SUNXI
|
|
#include <linux/sunxi-gpio.h>
|
|
#endif
|
|
|
|
#define SY6974_REG_0 0x00
|
|
#define SY6974_REG_1 0x01
|
|
#define SY6974_REG_2 0x02
|
|
#define SY6974_REG_3 0x03
|
|
#define SY6974_REG_4 0x04
|
|
#define SY6974_REG_5 0x05
|
|
#define SY6974_REG_6 0x06
|
|
#define SY6974_REG_7 0x07
|
|
#define SY6974_REG_8 0x08
|
|
#define SY6974_REG_9 0x09
|
|
#define SY6974_REG_10 0x0A
|
|
#define SY6974_REG_11 0x0B
|
|
|
|
#define SY6974_MANUFACTURER "Silergy"
|
|
|
|
#define SY6974_ILIM_SET_DELAY 1000 /* msec */
|
|
|
|
enum sy6974_fields {
|
|
F_EN_HIZ, F_STAT_DIS, F_IINLIM, /* REG 0 */
|
|
F_PFM_DIS, F_WD_RST, F_OTG_CONFIG, F_CHG_CONFIG, /* REG 1 */
|
|
F_SYS_MIN, F_OTG_BAT, /* REG 1 */
|
|
F_BOOST_LIM, F_Q1_FULLON, F_ICHG, /* REG 2 */
|
|
F_IPRECHG, F_ITERM, /* REG 3 */
|
|
F_VREG, F_TOPOFF_TIMER, F_VRECHG, /* REG 4 */
|
|
F_EN_TERM, F_WATCHDOG, F_EN_TIMER, F_CHG_TIMER, /* REG 5 */
|
|
F_TREG, F_JEITA_ISET, /* REG 5 */
|
|
F_OVP, F_BOOSTV, F_VINDPM, /* REG 6 */
|
|
F_FORCE_INDET, F_TMR2X_EN, F_BATFET_DIS, F_JEITA_VSET, /* REG 7 */
|
|
F_BATFET_DLY, F_BATFET_RST_EN, F_VDPM_BAT_TRACK, /* REG 7 */
|
|
F_BUS_STAT, F_CHRG_STAT, F_PG_STAT, F_THERM_STAT, /* REG 8 */
|
|
F_VSYS_STAT, /* REG 8 */
|
|
F_FAULT, /* REG 9 */
|
|
F_BUS_GD, F_VDPM_STAT, F_IDPM_STAT, F_TOPOFF_ACTIVE, /* REG 10 */
|
|
F_ACOV_STAT, F_VINDPM_INT_MASK, F_IINDPM_INT_MASK, /* REG 10 */
|
|
F_REG_RST, F_PN, F_DEV_REV, /* REG 11 */
|
|
F_MAX_FIELDS
|
|
};
|
|
|
|
/* initial field values, converted from uV/uA */
|
|
struct sy6974_init_data {
|
|
u8 ichg; /* charge current */
|
|
u8 vbat; /* regulation voltage */
|
|
u8 iterm; /* termination current */
|
|
u8 iilimit; /* input current limit */
|
|
u8 vovp; /* over voltage protection voltage */
|
|
u8 vindpm; /* VDMP input threshold voltage */
|
|
u8 sdchg; /* shutdown charge current */
|
|
};
|
|
|
|
struct sy6974_state {
|
|
u8 status;
|
|
u8 fault;
|
|
bool power_good;
|
|
};
|
|
|
|
struct sy6974_device {
|
|
struct i2c_client *client;
|
|
struct device *dev;
|
|
struct power_supply *charger;
|
|
|
|
struct regmap *rmap;
|
|
struct regmap_field *rmap_fields[F_MAX_FIELDS];
|
|
|
|
/*struct gpio_desc *pg;*/
|
|
|
|
/*struct delayed_work iilimit_setup_work;*/
|
|
|
|
struct sy6974_init_data init_data;
|
|
struct sy6974_state state;
|
|
|
|
struct mutex lock; /* protect state data */
|
|
|
|
/*bool iilimit_autoset_enable;*/
|
|
};
|
|
|
|
static bool sy6974_is_volatile_reg(struct device *dev, unsigned int reg)
|
|
{
|
|
switch (reg) {
|
|
case SY6974_REG_8:
|
|
case SY6974_REG_9:
|
|
return false;
|
|
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
static const struct regmap_config sy6974_regmap_config = {
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
|
|
.max_register = SY6974_REG_11,
|
|
.cache_type = REGCACHE_NONE,
|
|
|
|
.volatile_reg = sy6974_is_volatile_reg,
|
|
};
|
|
|
|
static const struct reg_field sy6974_reg_fields[] = {
|
|
/* REG 0 */
|
|
[F_EN_HIZ] = REG_FIELD(SY6974_REG_0, 7, 7),
|
|
[F_STAT_DIS] = REG_FIELD(SY6974_REG_0, 5, 6),
|
|
[F_IINLIM] = REG_FIELD(SY6974_REG_0, 0, 4),
|
|
/* REG 1 */
|
|
[F_PFM_DIS] = REG_FIELD(SY6974_REG_1, 7, 7),
|
|
[F_WD_RST] = REG_FIELD(SY6974_REG_1, 6, 6),
|
|
[F_OTG_CONFIG] = REG_FIELD(SY6974_REG_1, 5, 5),
|
|
[F_CHG_CONFIG] = REG_FIELD(SY6974_REG_1, 4, 4),
|
|
[F_SYS_MIN] = REG_FIELD(SY6974_REG_1, 1, 3),
|
|
[F_OTG_BAT] = REG_FIELD(SY6974_REG_1, 0, 0),
|
|
/* REG 2 */
|
|
[F_BOOST_LIM] = REG_FIELD(SY6974_REG_2, 7, 7),
|
|
[F_Q1_FULLON] = REG_FIELD(SY6974_REG_2, 6, 6),
|
|
[F_ICHG] = REG_FIELD(SY6974_REG_2, 0, 5),
|
|
/* REG 3 */
|
|
[F_IPRECHG] = REG_FIELD(SY6974_REG_3, 4, 7),
|
|
[F_ITERM] = REG_FIELD(SY6974_REG_3, 0, 3),
|
|
/* REG 4 */
|
|
[F_VREG] = REG_FIELD(SY6974_REG_4, 3, 7),
|
|
[F_TOPOFF_TIMER] = REG_FIELD(SY6974_REG_4, 1, 2),
|
|
[F_VRECHG] = REG_FIELD(SY6974_REG_4, 0, 0),
|
|
/* REG 5 */
|
|
[F_EN_TERM] = REG_FIELD(SY6974_REG_5, 7, 7),
|
|
[F_WATCHDOG] = REG_FIELD(SY6974_REG_5, 4, 5),
|
|
[F_EN_TIMER] = REG_FIELD(SY6974_REG_5, 3, 3),
|
|
[F_CHG_TIMER] = REG_FIELD(SY6974_REG_5, 2, 2),
|
|
[F_TREG] = REG_FIELD(SY6974_REG_5, 1, 1),
|
|
[F_JEITA_ISET] = REG_FIELD(SY6974_REG_5, 0, 0),
|
|
/* REG 6 */
|
|
[F_OVP] = REG_FIELD(SY6974_REG_6, 6, 7),
|
|
[F_BOOSTV] = REG_FIELD(SY6974_REG_6, 4, 5),
|
|
[F_VINDPM] = REG_FIELD(SY6974_REG_6, 0, 3),
|
|
/* REG 7 */
|
|
[F_FORCE_INDET] = REG_FIELD(SY6974_REG_7, 7, 7),
|
|
[F_TMR2X_EN] = REG_FIELD(SY6974_REG_7, 6, 6),
|
|
[F_BATFET_DIS] = REG_FIELD(SY6974_REG_7, 5, 5),
|
|
[F_JEITA_VSET] = REG_FIELD(SY6974_REG_7, 4, 4),
|
|
[F_BATFET_DLY] = REG_FIELD(SY6974_REG_7, 3, 3),
|
|
[F_BATFET_RST_EN] = REG_FIELD(SY6974_REG_7, 2, 2),
|
|
[F_VDPM_BAT_TRACK] = REG_FIELD(SY6974_REG_7, 0, 1),
|
|
/* REG 8 */
|
|
[F_BUS_STAT] = REG_FIELD(SY6974_REG_8, 5, 7),
|
|
[F_CHRG_STAT] = REG_FIELD(SY6974_REG_8, 3, 4),
|
|
[F_PG_STAT] = REG_FIELD(SY6974_REG_8, 2, 2),
|
|
[F_THERM_STAT] = REG_FIELD(SY6974_REG_8, 1, 1),
|
|
[F_VSYS_STAT] = REG_FIELD(SY6974_REG_8, 0, 0),
|
|
/* REG 9 */
|
|
[F_FAULT] = REG_FIELD(SY6974_REG_9, 0, 7),
|
|
/* REG 10 */
|
|
[F_BUS_GD] = REG_FIELD(SY6974_REG_10, 7, 7),
|
|
[F_VDPM_STAT] = REG_FIELD(SY6974_REG_10, 6, 6),
|
|
[F_IDPM_STAT] = REG_FIELD(SY6974_REG_10, 5, 5),
|
|
[F_TOPOFF_ACTIVE] = REG_FIELD(SY6974_REG_10, 3, 3),
|
|
[F_ACOV_STAT] = REG_FIELD(SY6974_REG_10, 2, 2),
|
|
[F_VINDPM_INT_MASK] = REG_FIELD(SY6974_REG_10, 1, 1),
|
|
[F_IINDPM_INT_MASK] = REG_FIELD(SY6974_REG_10, 0, 0),
|
|
/* REG 11 */
|
|
[F_REG_RST] = REG_FIELD(SY6974_REG_10, 7, 7),
|
|
[F_PN] = REG_FIELD(SY6974_REG_10, 3, 6),
|
|
[F_DEV_REV] = REG_FIELD(SY6974_REG_10, 0, 1)
|
|
};
|
|
|
|
enum sy6974_table_ids {
|
|
TBL_IINLIM,
|
|
TBL_ICHG,
|
|
TBL_IPRECHG,
|
|
TBL_ITERM,
|
|
TBL_VREG,
|
|
TBL_TOPOFF_TIMER,
|
|
TBL_VINDPM,
|
|
|
|
TBL_SYS_MIN,
|
|
TBL_OVP,
|
|
};
|
|
|
|
struct sy6974_range {
|
|
u32 min;
|
|
u32 max;
|
|
u32 step;
|
|
};
|
|
|
|
struct sy6974_lookup {
|
|
const u32 *tbl;
|
|
u32 size;
|
|
};
|
|
|
|
static const u32 sy6974_sys_min_tbl[] = { 2600, 2800, 3000, 3200, 3400,
|
|
3500, 3600, 3700};
|
|
static const u32 sy6974_ovp_tbl[] = { 5500, 6500, 10500, 14000};
|
|
|
|
static const union {
|
|
struct sy6974_range rt;
|
|
struct sy6974_lookup lt;
|
|
} sy6974_tables[] = {
|
|
/* range tables */
|
|
[TBL_IINLIM] = { .rt = {100, 3200, 100} }, /* mA */
|
|
[TBL_ICHG] = { .rt = {0, 3000, 60} }, /* mA */
|
|
[TBL_IPRECHG] = { .rt = {60, 780, 60} }, /* mA */
|
|
[TBL_ITERM] = { .rt = {60, 960, 60} }, /* mA */
|
|
[TBL_VREG] = { .rt = {3856, 4624, 32} }, /* mV */
|
|
[TBL_TOPOFF_TIMER] = { .rt = {0, 45, 15} }, /* min */
|
|
[TBL_VINDPM] = { .rt = {3900, 5400, 100} }, /* mV */
|
|
/* lookup tables */
|
|
[TBL_SYS_MIN] = { .lt = {
|
|
sy6974_sys_min_tbl,
|
|
ARRAY_SIZE(sy6974_sys_min_tbl)}
|
|
},
|
|
[TBL_OVP] = { .lt = {sy6974_ovp_tbl, ARRAY_SIZE(sy6974_ovp_tbl)} },
|
|
};
|
|
|
|
static u8 sy6974_find_idx(u32 value, enum sy6974_table_ids id)
|
|
{
|
|
u8 idx;
|
|
|
|
if (id >= TBL_SYS_MIN) {
|
|
const u32 *tbl = sy6974_tables[id].lt.tbl;
|
|
u32 tbl_size = sy6974_tables[id].lt.size;
|
|
|
|
for (idx = 1; idx < tbl_size && tbl[idx] <= value; idx++)
|
|
;
|
|
} else {
|
|
const struct sy6974_range *rtbl = &sy6974_tables[id].rt;
|
|
u8 rtbl_size;
|
|
|
|
rtbl_size = (rtbl->max - rtbl->min) / rtbl->step + 1;
|
|
|
|
for (idx = 1;
|
|
idx < rtbl_size && (idx * rtbl->step + rtbl->min <= value);
|
|
idx++)
|
|
;
|
|
}
|
|
|
|
return idx - 1;
|
|
}
|
|
|
|
|
|
static u32 sy6974_find_val_max(enum sy6974_table_ids id)
|
|
{
|
|
const struct sy6974_range *rtbl;
|
|
|
|
/* lookup table? */
|
|
if (id >= TBL_SYS_MIN)
|
|
return sy6974_tables[id].lt.tbl[sy6974_tables[id].lt.size-1];
|
|
|
|
/* range table */
|
|
rtbl = &sy6974_tables[id].rt;
|
|
|
|
return rtbl->max;
|
|
}
|
|
|
|
static u32 sy6974_find_val(u8 idx, enum sy6974_table_ids id)
|
|
{
|
|
const struct sy6974_range *rtbl;
|
|
|
|
/* lookup table? */
|
|
if (id >= TBL_SYS_MIN)
|
|
return sy6974_tables[id].lt.tbl[idx];
|
|
|
|
/* range table */
|
|
rtbl = &sy6974_tables[id].rt;
|
|
|
|
return (rtbl->min + idx * rtbl->step);
|
|
}
|
|
|
|
static int sy6974_field_read(struct sy6974_device *sy,
|
|
enum sy6974_fields field_id)
|
|
{
|
|
int ret;
|
|
int val;
|
|
|
|
ret = regmap_field_read(sy->rmap_fields[field_id], &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return val;
|
|
}
|
|
|
|
static int sy6974_field_write(struct sy6974_device *sy,
|
|
enum sy6974_fields field_id, u8 val)
|
|
{
|
|
return regmap_field_write(sy->rmap_fields[field_id], val);
|
|
}
|
|
|
|
enum sy6974_status {
|
|
STATUS_NOT_CHARGING,
|
|
STATUS_PRE_CHARGE,
|
|
STATUS_FAST_CHARGE,
|
|
STATUS_CHARGE_TERM,
|
|
};
|
|
|
|
static bool sy6974_state_changed(struct sy6974_device *sy,
|
|
struct sy6974_state *new_state)
|
|
{
|
|
int ret;
|
|
|
|
mutex_lock(&sy->lock);
|
|
ret = (sy->state.status != new_state->status ||
|
|
sy->state.fault != new_state->fault ||
|
|
sy->state.power_good != new_state->power_good);
|
|
mutex_unlock(&sy->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sy6974_get_chip_state(struct sy6974_device *sy,
|
|
struct sy6974_state *state)
|
|
{
|
|
int ret;
|
|
|
|
ret = sy6974_field_read(sy, F_CHRG_STAT);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
state->status = ret;
|
|
|
|
ret = sy6974_field_read(sy, F_FAULT);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
state->fault = ret;
|
|
|
|
ret = sy6974_field_read(sy, F_PG_STAT);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
state->power_good = ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static irqreturn_t sy6974_irq_handler_thread(int irq, void *private)
|
|
{
|
|
int ret;
|
|
struct sy6974_device *sy = private;
|
|
struct sy6974_state state;
|
|
|
|
ret = sy6974_get_chip_state(sy, &state);
|
|
if (ret < 0)
|
|
return IRQ_HANDLED;
|
|
|
|
if (!sy6974_state_changed(sy, &state))
|
|
return IRQ_HANDLED;
|
|
|
|
dev_info(sy->dev, "state change: status/fault/pg=%d/%d/%d->%d/%d/%d\n",
|
|
sy->state.status, sy->state.fault, sy->state.power_good,
|
|
state.status, state.fault, state.power_good);
|
|
#if 0
|
|
if (state.power_good && !sy->state.power_good) {
|
|
/*
|
|
* PSEL Pull Up, current 500mA
|
|
* PSEL Pull Down, currnet 2.4A
|
|
* set iilimit here.
|
|
*/
|
|
sy6974_field_write(sy, F_IINLIM, sy->init_data.iilimit);
|
|
}
|
|
#endif
|
|
|
|
|
|
mutex_lock(&sy->lock);
|
|
sy->state = state;
|
|
mutex_unlock(&sy->lock);
|
|
|
|
power_supply_changed(sy->charger);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int sy6974_set_input_current_limit(struct sy6974_device *sy,
|
|
const union power_supply_propval *val)
|
|
{
|
|
unsigned int value = val->intval;
|
|
|
|
pr_debug("current=%d, iilimit=%d\n", value, sy->init_data.iilimit);
|
|
if (!value)
|
|
value = sy->init_data.iilimit;
|
|
else
|
|
value = sy6974_find_idx(value, TBL_IINLIM);
|
|
|
|
return sy6974_field_write(sy, F_IINLIM, value);
|
|
}
|
|
|
|
static int sy6974_get_input_current_limit(struct sy6974_device *sy,
|
|
union power_supply_propval *val)
|
|
{
|
|
int ret;
|
|
|
|
ret = sy6974_field_read(sy, F_IINLIM);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
val->intval = sy6974_find_val(ret, TBL_IINLIM);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sy6974_hw_init(struct sy6974_device *sy)
|
|
{
|
|
int ret;
|
|
int i;
|
|
struct sy6974_state state;
|
|
|
|
const struct {
|
|
int field;
|
|
u32 value;
|
|
} init_data[] = {
|
|
{F_ICHG, sy->init_data.ichg},
|
|
{F_VREG, sy->init_data.vbat},
|
|
{F_ITERM, sy->init_data.iterm},
|
|
{F_OVP, sy->init_data.vovp},
|
|
{F_VINDPM, sy->init_data.vindpm},
|
|
};
|
|
|
|
/*
|
|
* Disable the watchdog timer to prevent the IC from going back to
|
|
* default settings after 50 seconds of I2C inactivity.
|
|
*/
|
|
ret = sy6974_field_write(sy, F_WATCHDOG, 0x00);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* configure the charge currents and voltages */
|
|
for (i = 0; i < ARRAY_SIZE(init_data); i++) {
|
|
ret = sy6974_field_write(sy, init_data[i].field,
|
|
init_data[i].value);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
ret = sy6974_get_chip_state(sy, &state);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
mutex_lock(&sy->lock);
|
|
sy->state = state;
|
|
mutex_unlock(&sy->lock);
|
|
|
|
/* program fixed input current limit */
|
|
ret = sy6974_field_write(sy, F_IINLIM, sy->init_data.iilimit);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t sy6974_show_ovp_voltage(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct power_supply *psy = dev_get_drvdata(dev);
|
|
struct sy6974_device *sy = power_supply_get_drvdata(psy);
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "%u\n",
|
|
sy6974_find_val(sy->init_data.vovp, TBL_OVP));
|
|
}
|
|
static DEVICE_ATTR(ovp_voltage, 0444, sy6974_show_ovp_voltage, NULL);
|
|
static struct attribute *sy6974_charger_attr[] = {
|
|
&dev_attr_ovp_voltage.attr,
|
|
NULL,
|
|
};
|
|
static const struct attribute_group sy6974_attr_group = {
|
|
.attrs = sy6974_charger_attr,
|
|
};
|
|
|
|
#define WATCHDOG_FAULT_MASK (0x80)
|
|
#define BOOST_FAULT_MASK (0x40)
|
|
#define CHRG_FAULT_MASK (0x30)
|
|
enum chrg_fault_mask {
|
|
CHRG_FAULT_INPUT = 0x01,
|
|
CHRG_FAULT_THERMAL_SHUTDOWN = 0x02,
|
|
CHRG_FAULT_CHARGE_SAFETY = 0x03,
|
|
};
|
|
#define BAT_FAULT_MASK (0x08)
|
|
#define NTC_FAULT_MASK (0x07)
|
|
enum ntc_fault {
|
|
NTC_FAULT_WARM = 0x02,
|
|
NTC_FAULT_COOL = 0x03,
|
|
NTC_FAULT_COLD = 0x05,
|
|
NTC_FAULT_HOT = 0x06,
|
|
};
|
|
|
|
static int sy6974_power_supply_get_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct sy6974_device *sy = power_supply_get_drvdata(psy);
|
|
struct sy6974_state state;
|
|
|
|
mutex_lock(&sy->lock);
|
|
sy6974_get_chip_state(sy, &state);
|
|
sy->state = state;
|
|
mutex_unlock(&sy->lock);
|
|
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
if (!state.power_good)
|
|
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
else if (state.status == STATUS_NOT_CHARGING)
|
|
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
else if (state.status == STATUS_PRE_CHARGE ||
|
|
state.status == STATUS_FAST_CHARGE)
|
|
val->intval = POWER_SUPPLY_STATUS_CHARGING;
|
|
else if (state.status == STATUS_CHARGE_TERM)
|
|
val->intval = POWER_SUPPLY_STATUS_FULL;
|
|
else
|
|
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_MANUFACTURER:
|
|
val->strval = SY6974_MANUFACTURER;
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_MODEL_NAME:
|
|
val->strval = "sy6974";
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
val->intval = state.power_good;
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_HEALTH:
|
|
/* NTC_FAULT */
|
|
if (state.fault & NTC_FAULT_MASK) {
|
|
switch (state.fault & NTC_FAULT_MASK) {
|
|
case NTC_FAULT_COOL:
|
|
case NTC_FAULT_COLD:
|
|
val->intval = POWER_SUPPLY_HEALTH_COLD;
|
|
break;
|
|
case NTC_FAULT_WARM:
|
|
case NTC_FAULT_HOT:
|
|
val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
|
|
break;
|
|
default:
|
|
val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
|
|
break;
|
|
}
|
|
} else if (state.fault & BAT_FAULT_MASK) {
|
|
val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
|
|
} else if (state.fault & CHRG_FAULT_MASK) {
|
|
switch (state.fault & CHRG_FAULT_MASK) {
|
|
case CHRG_FAULT_INPUT:
|
|
val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
|
|
break;
|
|
case CHRG_FAULT_THERMAL_SHUTDOWN:
|
|
val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
|
|
break;
|
|
case CHRG_FAULT_CHARGE_SAFETY:
|
|
val->intval =
|
|
POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
|
|
break;
|
|
}
|
|
} else if (state.fault & BOOST_FAULT_MASK) {
|
|
val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
|
|
} else if (state.fault & WATCHDOG_FAULT_MASK) {
|
|
val->intval = POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE;
|
|
} else {
|
|
val->intval = POWER_SUPPLY_HEALTH_GOOD;
|
|
}
|
|
break;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
|
val->intval = sy6974_find_val(sy->init_data.ichg, TBL_ICHG);
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
|
val->intval = sy6974_find_val_max(TBL_ICHG);
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
|
|
val->intval = sy6974_find_val(sy->init_data.vbat, TBL_VREG);
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
|
|
val->intval = sy6974_find_val_max(TBL_VREG);
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
|
|
val->intval = sy6974_find_val(sy->init_data.iterm, TBL_ITERM);
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
|
return sy6974_get_input_current_limit(sy, val);
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sy6974_power_supply_set_property(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
const union power_supply_propval *val)
|
|
{
|
|
struct sy6974_device *sy = power_supply_get_drvdata(psy);
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
|
return sy6974_set_input_current_limit(sy, val);
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sy6974_power_supply_property_is_writeable(struct power_supply *psy,
|
|
enum power_supply_property psp)
|
|
{
|
|
return false;
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static enum power_supply_property sy6974_power_supply_props[] = {
|
|
POWER_SUPPLY_PROP_MANUFACTURER,
|
|
POWER_SUPPLY_PROP_MODEL_NAME,
|
|
POWER_SUPPLY_PROP_STATUS,
|
|
POWER_SUPPLY_PROP_ONLINE,
|
|
POWER_SUPPLY_PROP_HEALTH,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
|
|
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
|
|
};
|
|
|
|
static const struct power_supply_desc sy6974_power_supply_desc = {
|
|
.name = "sy6974-charger",
|
|
.type = POWER_SUPPLY_TYPE_USB,
|
|
.properties = sy6974_power_supply_props,
|
|
.num_properties = ARRAY_SIZE(sy6974_power_supply_props),
|
|
.get_property = sy6974_power_supply_get_property,
|
|
.set_property = sy6974_power_supply_set_property,
|
|
.property_is_writeable = sy6974_power_supply_property_is_writeable,
|
|
};
|
|
|
|
static char *sy6974_charger_supplied_to[] = {
|
|
"main-battery",
|
|
};
|
|
|
|
struct power_supply_config sy6974_psy_cfg = {
|
|
.supplied_to = sy6974_charger_supplied_to,
|
|
.num_supplicants = ARRAY_SIZE(sy6974_charger_supplied_to),
|
|
};
|
|
|
|
static int sy6974_power_supply_init(struct sy6974_device *sy)
|
|
{
|
|
sy6974_psy_cfg.drv_data = sy,
|
|
sy->charger = devm_power_supply_register(sy->dev,
|
|
&sy6974_power_supply_desc,
|
|
&sy6974_psy_cfg);
|
|
|
|
return PTR_ERR_OR_ZERO(sy->charger);
|
|
}
|
|
|
|
static int sy6974_fw_probe(struct sy6974_device *sy)
|
|
{
|
|
int ret;
|
|
u32 property;
|
|
struct device_node *np = sy->dev->of_node;
|
|
int gpio;
|
|
#ifdef CONFIG_ARCH_SUNXI
|
|
struct gpio_config flags;
|
|
#else
|
|
enum of_gpio_flags flags;
|
|
#endif
|
|
|
|
gpio = of_get_named_gpio_flags(np, "sy,irq-gpio", 0,
|
|
(enum of_gpio_flags *)&flags);
|
|
if (gpio > 0) {
|
|
#ifdef CONFIG_ARCH_SUNXI
|
|
char pin_name[32];
|
|
__u32 config;
|
|
|
|
/* wakeup_gpio set pull */
|
|
memset(pin_name, 0, sizeof(pin_name));
|
|
sunxi_gpio_to_name(gpio, pin_name);
|
|
config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_PUD, 1);
|
|
if (gpio < SUNXI_PL_BASE)
|
|
pin_config_set(SUNXI_PINCTRL, pin_name, config);
|
|
else
|
|
pin_config_set(SUNXI_R_PINCTRL, pin_name, config);
|
|
#endif
|
|
sy->client->irq = gpio_to_irq(gpio);
|
|
}
|
|
|
|
/* Required properties */
|
|
ret = device_property_read_u32(sy->dev, "sy,charge-current", &property);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
sy->init_data.ichg = sy6974_find_idx(property, TBL_ICHG);
|
|
pr_debug("charge-current=%d, ichg=%d\n", property, sy->init_data.ichg);
|
|
|
|
ret = device_property_read_u32(sy->dev, "sy,shutdown-current", &property);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
sy->init_data.sdchg = sy6974_find_idx(property, TBL_ICHG);
|
|
pr_debug("shutdown-current=%d, sdchg=%d\n", property, sy->init_data.sdchg);
|
|
|
|
ret = device_property_read_u32(sy->dev, "sy,battery-regulation-voltage",
|
|
&property);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
sy->init_data.vbat = sy6974_find_idx(property, TBL_VREG);
|
|
pr_debug("Vtarget=%d, vbat=%d\n", property, sy->init_data.vbat);
|
|
|
|
ret = device_property_read_u32(sy->dev, "sy,termination-current",
|
|
&property);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
sy->init_data.iterm = sy6974_find_idx(property, TBL_ITERM);
|
|
pr_debug("Iterm=%d, iterm=%d\n", property, sy->init_data.iterm);
|
|
|
|
/* Optional properties. If not provided use reasonable default. */
|
|
ret = device_property_read_u32(sy->dev, "sy,current-limit",
|
|
&property);
|
|
if (ret < 0) {
|
|
/*sy->iilimit_autoset_enable = true;*/
|
|
|
|
/*
|
|
* Explicitly set a default value which will be needed for
|
|
* devices that don't support the automatic setting of the input
|
|
* current limit through the charger type detection mechanism.
|
|
*/
|
|
sy->init_data.iilimit = sy6974_find_idx(500, TBL_IINLIM);
|
|
} else
|
|
sy->init_data.iilimit = sy6974_find_idx(property, TBL_IINLIM);
|
|
pr_debug("Ilimit=%d, iilimit=%d\n", property, sy->init_data.iilimit);
|
|
|
|
ret = device_property_read_u32(sy->dev, "sy,ovp-voltage",
|
|
&property);
|
|
if (ret < 0)
|
|
sy->init_data.vovp = sy6974_find_idx(6500, TBL_OVP);
|
|
else
|
|
sy->init_data.vovp = sy6974_find_idx(property, TBL_OVP);
|
|
pr_debug("Vovp=%d, vovp=%d\n", property, sy->init_data.vovp);
|
|
|
|
ret = device_property_read_u32(sy->dev, "sy,in-dpm-voltage",
|
|
&property);
|
|
if (ret < 0)
|
|
sy->init_data.vindpm = sy6974_find_idx(4500, TBL_VINDPM);
|
|
else
|
|
sy->init_data.vindpm =
|
|
sy6974_find_idx(property, TBL_VINDPM);
|
|
pr_debug("Vindpm=%d, vindpm=%d\n", property, sy->init_data.vindpm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sy6974_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
|
|
struct device *dev = &client->dev;
|
|
struct sy6974_device *sy;
|
|
int ret = 0;
|
|
int i;
|
|
|
|
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
|
|
dev_err(dev, "No support for SMBUS_BYTE_DATA\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
sy = devm_kzalloc(dev, sizeof(*sy), GFP_KERNEL);
|
|
if (!sy)
|
|
return -ENOMEM;
|
|
|
|
sy->client = client;
|
|
sy->dev = dev;
|
|
|
|
mutex_init(&sy->lock);
|
|
|
|
sy->rmap = devm_regmap_init_i2c(client, &sy6974_regmap_config);
|
|
if (IS_ERR(sy->rmap)) {
|
|
dev_err(dev, "failed to allocate register map\n");
|
|
return PTR_ERR(sy->rmap);
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sy6974_reg_fields); i++) {
|
|
/*const struct reg_field *reg_fields = sy6974_reg_fields;*/
|
|
sy->rmap_fields[i] = devm_regmap_field_alloc(dev, sy->rmap,
|
|
sy6974_reg_fields[i]);
|
|
if (IS_ERR(sy->rmap_fields[i])) {
|
|
dev_err(dev, "cannot allocate regmap field\n");
|
|
return PTR_ERR(sy->rmap_fields[i]);
|
|
}
|
|
}
|
|
|
|
i2c_set_clientdata(client, sy);
|
|
|
|
ret = sy6974_fw_probe(sy);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Cannot read device properties.\n");
|
|
return ret;
|
|
}
|
|
ret = sy6974_hw_init(sy);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Cannot initialize the chip.\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = sy6974_power_supply_init(sy);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Failed to register power supply\n");
|
|
return ret;
|
|
}
|
|
|
|
if (client->irq > 0) {
|
|
ret = devm_request_threaded_irq(dev, client->irq, NULL,
|
|
sy6974_irq_handler_thread,
|
|
IRQF_TRIGGER_FALLING |
|
|
IRQF_ONESHOT | IRQF_SHARED,
|
|
"sy7964", sy);
|
|
if (ret) {
|
|
dev_err(dev, " request IRQ#%d failed\n", client->irq);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = sysfs_create_group(&sy->charger->dev.kobj, &sy6974_attr_group);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Can't create sysfs entries\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sy6974_remove(struct i2c_client *client)
|
|
{
|
|
struct sy6974_device *sy = i2c_get_clientdata(client);
|
|
|
|
sysfs_remove_group(&sy->charger->dev.kobj, &sy6974_attr_group);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sy6974_shutdown(struct i2c_client *client)
|
|
{
|
|
#if 0
|
|
struct sy6974_device *sy = i2c_get_clientdata(client);
|
|
struct sy6974_state state;
|
|
|
|
/* shutdown charge current */
|
|
sy6974_field_write(sy, F_ICHG, sy->init_data.sdchg);
|
|
sy6974_get_chip_state(sy, &state);
|
|
if (state.status == STATUS_NOT_CHARGING) {
|
|
/* not charging, enter ship mode */
|
|
sy6974_field_write(sy, F_BATFET_DIS, 1);
|
|
sy6974_field_write(sy, F_BATFET_DLY, 0);
|
|
}
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int sy6974_suspend(struct device *dev)
|
|
{
|
|
#if 0
|
|
struct sy6974_device *sy = dev_get_drvdata(dev);
|
|
int ret = 0;
|
|
|
|
return ret;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static int sy6974_resume(struct device *dev)
|
|
{
|
|
#if 0
|
|
struct sy6974_device *sy = dev_get_drvdata(dev);
|
|
int ret;
|
|
/* signal userspace, maybe state changed while suspended */
|
|
power_supply_changed(sy->charger);
|
|
return ret;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
static const struct dev_pm_ops sy6974_pm = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(sy6974_suspend, sy6974_resume)
|
|
};
|
|
|
|
static const struct i2c_device_id sy6974_i2c_ids[] = {
|
|
{ "sy6974", 0 },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, sy6974_i2c_ids);
|
|
|
|
static const struct of_device_id sy6974_of_match[] = {
|
|
{ .compatible = "silergy,sy6974", },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, sy6974_of_match);
|
|
|
|
static struct i2c_driver sy6974_driver = {
|
|
.driver = {
|
|
.name = "sy6974-charger",
|
|
.of_match_table = of_match_ptr(sy6974_of_match),
|
|
.pm = &sy6974_pm,
|
|
},
|
|
.probe = sy6974_probe,
|
|
.remove = sy6974_remove,
|
|
.id_table = sy6974_i2c_ids,
|
|
.shutdown = sy6974_shutdown,
|
|
};
|
|
module_i2c_driver(sy6974_driver);
|
|
|
|
MODULE_AUTHOR("ForeverCai <caiyongheng@allwinnertech.com>");
|
|
MODULE_DESCRIPTION("sy6974 charger driver");
|
|
MODULE_LICENSE("GPL");
|