Commit 76f901eb authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'for-v3.5' of git://git.infradead.org/battery-2.6

Pull battery updates from Anton Vorontsov:
 "A bunch of fixes for v3.5, nothing extraordinary."

* tag 'for-v3.5' of git://git.infradead.org/battery-2.6: (27 commits)
  smb347-charger: Include missing <linux/err.h>
  smb347-charger: Clean up battery attributes
  max17042_battery: Add support for max17047/50 chip
  sbs-battery.c: Capacity attr = remaining relative capacity
  isp1704_charger: Use after free on probe error
  ds2781_battery: Use DS2781_PARAM_EEPROM_SIZE and DS2781_USER_EEPROM_SIZE
  power_supply: Fix a typo in BATTERY_DS2781 Kconfig entry
  charger-manager: Provide cm_notify_event function for in-kernel use
  charger-manager: Poll battery health in normal state
  smb347-charger: Convert to regmap API
  smb347-charger: Move IRQ enabling to the end of probe
  smb347-charger: Rename few functions to match better what they are doing
  smb347-charger: Convert to use module_i2c_driver()
  smb347_charger: Cleanup power supply registration code in probe
  ab8500: Clean up probe routines
  ab8500_fg: Harden platform data check
  ab8500_btemp: Harden platform data check
  ab8500_charger: Harden platform data check
  MAINTAINERS: Fix 'F' entry for the power supply class
  max17042_battery: Handle irq request failure case
  ...
parents bd0e162d 96facd23
......@@ -44,6 +44,16 @@ Charger Manager supports the following:
Normally, the platform will need to resume and suspend some devices
that are used by Charger Manager.
* Support for premature full-battery event handling
If the battery voltage drops by "fullbatt_vchkdrop_uV" after
"fullbatt_vchkdrop_ms" from the full-battery event, the framework
restarts charging. This check is also performed while suspended by
setting wakeup time accordingly and using suspend_again.
* Support for uevent-notify
With the charger-related events, the device sends
notification to users with UEVENT.
2. Global Charger-Manager Data related with suspend_again
========================================================
In order to setup Charger Manager with suspend-again feature
......@@ -55,7 +65,7 @@ if there are multiple batteries. If there are multiple batteries, the
multiple instances of Charger Manager share the same charger_global_desc
and it will manage in-suspend monitoring for all instances of Charger Manager.
The user needs to provide all the two entries properly in order to activate
The user needs to provide all the three entries properly in order to activate
in-suspend monitoring:
struct charger_global_desc {
......@@ -74,6 +84,11 @@ bool (*rtc_only_wakeup)(void);
same struct. If there is any other wakeup source triggered the
wakeup, it should return false. If the "rtc" is the only wakeup
reason, it should return true.
bool assume_timer_stops_in_suspend;
: if true, Charger Manager assumes that
the timer (CM uses jiffies as timer) stops during suspend. Then, CM
assumes that the suspend-duration is same as the alarm length.
};
3. How to setup suspend_again
......@@ -111,6 +126,16 @@ enum polling_modes polling_mode;
CM_POLL_CHARGING_ONLY: poll this battery if and only if the
battery is being charged.
unsigned int fullbatt_vchkdrop_ms;
unsigned int fullbatt_vchkdrop_uV;
: If both have non-zero values, Charger Manager will check the
battery voltage drop fullbatt_vchkdrop_ms after the battery is fully
charged. If the voltage drop is over fullbatt_vchkdrop_uV, Charger
Manager will try to recharge the battery by disabling and enabling
chargers. Recharge with voltage drop condition only (without delay
condition) is needed to be implemented with hardware interrupts from
fuel gauges or charger devices/chips.
unsigned int fullbatt_uV;
: If specified with a non-zero value, Charger Manager assumes
that the battery is full (capacity = 100) if the battery is not being
......@@ -122,6 +147,8 @@ unsigned int polling_interval_ms;
this battery every polling_interval_ms or more frequently.
enum data_source battery_present;
: CM_BATTERY_PRESENT: assume that the battery exists.
CM_NO_BATTERY: assume that the battery does not exists.
CM_FUEL_GAUGE: get battery presence information from fuel gauge.
CM_CHARGER_STAT: get battery presence from chargers.
......@@ -151,7 +178,17 @@ bool measure_battery_temp;
the value of measure_battery_temp.
};
5. Other Considerations
5. Notify Charger-Manager of charger events: cm_notify_event()
=========================================================
If there is an charger event is required to notify
Charger Manager, a charger device driver that triggers the event can call
cm_notify_event(psy, type, msg) to notify the corresponding Charger Manager.
In the function, psy is the charger driver's power_supply pointer, which is
associated with Charger-Manager. The parameter "type"
is the same as irq's type (enum cm_event_types). The event message "msg" is
optional and is effective only if the event type is "UNDESCRIBED" or "OTHERS".
6. Other Considerations
=======================
At the charger/battery-related events such as battery-pulled-out,
......
......@@ -84,6 +84,8 @@ are already charged or discharging, 'n/a' can be displayed (or
HEALTH - represents health of the battery, values corresponds to
POWER_SUPPLY_HEALTH_*, defined in battery.h.
VOLTAGE_OCV - open circuit voltage of the battery.
VOLTAGE_MAX_DESIGN, VOLTAGE_MIN_DESIGN - design values for maximal and
minimal power supply voltages. Maximal/minimal means values of voltages
when battery considered "full"/"empty" at normal conditions. Yes, there is
......
......@@ -5337,7 +5337,7 @@ M: David Woodhouse <dwmw2@infradead.org>
T: git git://git.infradead.org/battery-2.6.git
S: Maintained
F: include/linux/power_supply.h
F: drivers/power/power_supply*
F: drivers/power/
PNP SUPPORT
M: Adam Belay <abelay@mit.edu>
......
menuconfig POWER_SUPPLY
tristate "Power supply class support"
bool "Power supply class support"
help
Say Y here to enable power supply class support. This allows
power supply (batteries, AC, USB) monitoring by userspace
......@@ -77,7 +77,7 @@ config BATTERY_DS2780
Say Y here to enable support for batteries with ds2780 chip.
config BATTERY_DS2781
tristate "2781 battery driver"
tristate "DS2781 battery driver"
depends on HAS_IOMEM
select W1
select W1_SLAVE_DS2781
......@@ -181,14 +181,15 @@ config BATTERY_MAX17040
to operate with a single lithium cell
config BATTERY_MAX17042
tristate "Maxim MAX17042/8997/8966 Fuel Gauge"
tristate "Maxim MAX17042/17047/17050/8997/8966 Fuel Gauge"
depends on I2C
help
MAX17042 is fuel-gauge systems for lithium-ion (Li+) batteries
in handheld and portable equipment. The MAX17042 is configured
to operate with a single lithium cell. MAX8997 and MAX8966 are
multi-function devices that include fuel gauages that are compatible
with MAX17042.
with MAX17042. This driver also supports max17047/50 chips which are
improved version of max17042.
config BATTERY_Z2
tristate "Z2 battery driver"
......@@ -291,6 +292,7 @@ config CHARGER_MAX8998
config CHARGER_SMB347
tristate "Summit Microelectronics SMB347 Battery Charger"
depends on I2C
select REGMAP_I2C
help
Say Y to include support for Summit Microelectronics SMB347
Battery Charger.
......
......@@ -964,10 +964,15 @@ static int __devinit ab8500_btemp_probe(struct platform_device *pdev)
{
int irq, i, ret = 0;
u8 val;
struct abx500_bm_plat_data *plat_data;
struct abx500_bm_plat_data *plat_data = pdev->dev.platform_data;
struct ab8500_btemp *di;
if (!plat_data) {
dev_err(&pdev->dev, "No platform data\n");
return -EINVAL;
}
struct ab8500_btemp *di =
kzalloc(sizeof(struct ab8500_btemp), GFP_KERNEL);
di = kzalloc(sizeof(*di), GFP_KERNEL);
if (!di)
return -ENOMEM;
......@@ -977,7 +982,6 @@ static int __devinit ab8500_btemp_probe(struct platform_device *pdev)
di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
/* get btemp specific platform data */
plat_data = pdev->dev.platform_data;
di->pdata = plat_data->btemp;
if (!di->pdata) {
dev_err(di->dev, "no btemp platform data supplied\n");
......
......@@ -2534,10 +2534,15 @@ static int __devexit ab8500_charger_remove(struct platform_device *pdev)
static int __devinit ab8500_charger_probe(struct platform_device *pdev)
{
int irq, i, charger_status, ret = 0;
struct abx500_bm_plat_data *plat_data;
struct abx500_bm_plat_data *plat_data = pdev->dev.platform_data;
struct ab8500_charger *di;
struct ab8500_charger *di =
kzalloc(sizeof(struct ab8500_charger), GFP_KERNEL);
if (!plat_data) {
dev_err(&pdev->dev, "No platform data\n");
return -EINVAL;
}
di = kzalloc(sizeof(*di), GFP_KERNEL);
if (!di)
return -ENOMEM;
......@@ -2550,9 +2555,7 @@ static int __devinit ab8500_charger_probe(struct platform_device *pdev)
spin_lock_init(&di->usb_state.usb_lock);
/* get charger specific platform data */
plat_data = pdev->dev.platform_data;
di->pdata = plat_data->charger;
if (!di->pdata) {
dev_err(di->dev, "no charger platform data supplied\n");
ret = -EINVAL;
......
......@@ -2446,10 +2446,15 @@ static int __devinit ab8500_fg_probe(struct platform_device *pdev)
{
int i, irq;
int ret = 0;
struct abx500_bm_plat_data *plat_data;
struct abx500_bm_plat_data *plat_data = pdev->dev.platform_data;
struct ab8500_fg *di;
if (!plat_data) {
dev_err(&pdev->dev, "No platform data\n");
return -EINVAL;
}
struct ab8500_fg *di =
kzalloc(sizeof(struct ab8500_fg), GFP_KERNEL);
di = kzalloc(sizeof(*di), GFP_KERNEL);
if (!di)
return -ENOMEM;
......@@ -2461,7 +2466,6 @@ static int __devinit ab8500_fg_probe(struct platform_device *pdev)
di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
/* get fg specific platform data */
plat_data = pdev->dev.platform_data;
di->pdata = plat_data->fg;
if (!di->pdata) {
dev_err(di->dev, "no fg platform data supplied\n");
......
......@@ -23,6 +23,16 @@
#include <linux/power/charger-manager.h>
#include <linux/regulator/consumer.h>
static const char * const default_event_names[] = {
[CM_EVENT_UNKNOWN] = "Unknown",
[CM_EVENT_BATT_FULL] = "Battery Full",
[CM_EVENT_BATT_IN] = "Battery Inserted",
[CM_EVENT_BATT_OUT] = "Battery Pulled Out",
[CM_EVENT_EXT_PWR_IN_OUT] = "External Power Attach/Detach",
[CM_EVENT_CHG_START_STOP] = "Charging Start/Stop",
[CM_EVENT_OTHERS] = "Other battery events"
};
/*
* Regard CM_JIFFIES_SMALL jiffies is small enough to ignore for
* delayed works so that we can run delayed works with CM_JIFFIES_SMALL
......@@ -57,6 +67,12 @@ static bool cm_suspended;
static bool cm_rtc_set;
static unsigned long cm_suspend_duration_ms;
/* About normal (not suspended) monitoring */
static unsigned long polling_jiffy = ULONG_MAX; /* ULONG_MAX: no polling */
static unsigned long next_polling; /* Next appointed polling time */
static struct workqueue_struct *cm_wq; /* init at driver add */
static struct delayed_work cm_monitor_work; /* init at driver add */
/* Global charger-manager description */
static struct charger_global_desc *g_desc; /* init with setup_charger_manager */
......@@ -71,6 +87,11 @@ static bool is_batt_present(struct charger_manager *cm)
int i, ret;
switch (cm->desc->battery_present) {
case CM_BATTERY_PRESENT:
present = true;
break;
case CM_NO_BATTERY:
break;
case CM_FUEL_GAUGE:
ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
POWER_SUPPLY_PROP_PRESENT, &val);
......@@ -278,6 +299,26 @@ static int try_charger_enable(struct charger_manager *cm, bool enable)
return err;
}
/**
* try_charger_restart - Restart charging.
* @cm: the Charger Manager representing the battery.
*
* Restart charging by turning off and on the charger.
*/
static int try_charger_restart(struct charger_manager *cm)
{
int err;
if (cm->emergency_stop)
return -EAGAIN;
err = try_charger_enable(cm, false);
if (err)
return err;
return try_charger_enable(cm, true);
}
/**
* uevent_notify - Let users know something has changed.
* @cm: the Charger Manager representing the battery.
......@@ -333,6 +374,46 @@ static void uevent_notify(struct charger_manager *cm, const char *event)
dev_info(cm->dev, event);
}
/**
* fullbatt_vchk - Check voltage drop some times after "FULL" event.
* @work: the work_struct appointing the function
*
* If a user has designated "fullbatt_vchkdrop_ms/uV" values with
* charger_desc, Charger Manager checks voltage drop after the battery
* "FULL" event. It checks whether the voltage has dropped more than
* fullbatt_vchkdrop_uV by calling this function after fullbatt_vchkrop_ms.
*/
static void fullbatt_vchk(struct work_struct *work)
{
struct delayed_work *dwork = to_delayed_work(work);
struct charger_manager *cm = container_of(dwork,
struct charger_manager, fullbatt_vchk_work);
struct charger_desc *desc = cm->desc;
int batt_uV, err, diff;
/* remove the appointment for fullbatt_vchk */
cm->fullbatt_vchk_jiffies_at = 0;
if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms)
return;
err = get_batt_uV(cm, &batt_uV);
if (err) {
dev_err(cm->dev, "%s: get_batt_uV error(%d).\n", __func__, err);
return;
}
diff = cm->fullbatt_vchk_uV;
diff -= batt_uV;
dev_dbg(cm->dev, "VBATT dropped %duV after full-batt.\n", diff);
if (diff > desc->fullbatt_vchkdrop_uV) {
try_charger_restart(cm);
uevent_notify(cm, "Recharge");
}
}
/**
* _cm_monitor - Monitor the temperature and return true for exceptions.
* @cm: the Charger Manager representing the battery.
......@@ -392,6 +473,131 @@ static bool cm_monitor(void)
return stop;
}
/**
* _setup_polling - Setup the next instance of polling.
* @work: work_struct of the function _setup_polling.
*/
static void _setup_polling(struct work_struct *work)
{
unsigned long min = ULONG_MAX;
struct charger_manager *cm;
bool keep_polling = false;
unsigned long _next_polling;
mutex_lock(&cm_list_mtx);
list_for_each_entry(cm, &cm_list, entry) {
if (is_polling_required(cm) && cm->desc->polling_interval_ms) {
keep_polling = true;
if (min > cm->desc->polling_interval_ms)
min = cm->desc->polling_interval_ms;
}
}
polling_jiffy = msecs_to_jiffies(min);
if (polling_jiffy <= CM_JIFFIES_SMALL)
polling_jiffy = CM_JIFFIES_SMALL + 1;
if (!keep_polling)
polling_jiffy = ULONG_MAX;
if (polling_jiffy == ULONG_MAX)
goto out;
WARN(cm_wq == NULL, "charger-manager: workqueue not initialized"
". try it later. %s\n", __func__);
_next_polling = jiffies + polling_jiffy;
if (!delayed_work_pending(&cm_monitor_work) ||
(delayed_work_pending(&cm_monitor_work) &&
time_after(next_polling, _next_polling))) {
cancel_delayed_work_sync(&cm_monitor_work);
next_polling = jiffies + polling_jiffy;
queue_delayed_work(cm_wq, &cm_monitor_work, polling_jiffy);
}
out:
mutex_unlock(&cm_list_mtx);
}
static DECLARE_WORK(setup_polling, _setup_polling);
/**
* cm_monitor_poller - The Monitor / Poller.
* @work: work_struct of the function cm_monitor_poller
*
* During non-suspended state, cm_monitor_poller is used to poll and monitor
* the batteries.
*/
static void cm_monitor_poller(struct work_struct *work)
{
cm_monitor();
schedule_work(&setup_polling);
}
/**
* fullbatt_handler - Event handler for CM_EVENT_BATT_FULL
* @cm: the Charger Manager representing the battery.
*/
static void fullbatt_handler(struct charger_manager *cm)
{
struct charger_desc *desc = cm->desc;
if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms)
goto out;
if (cm_suspended)
device_set_wakeup_capable(cm->dev, true);
if (delayed_work_pending(&cm->fullbatt_vchk_work))
cancel_delayed_work(&cm->fullbatt_vchk_work);
queue_delayed_work(cm_wq, &cm->fullbatt_vchk_work,
msecs_to_jiffies(desc->fullbatt_vchkdrop_ms));
cm->fullbatt_vchk_jiffies_at = jiffies + msecs_to_jiffies(
desc->fullbatt_vchkdrop_ms);
if (cm->fullbatt_vchk_jiffies_at == 0)
cm->fullbatt_vchk_jiffies_at = 1;
out:
dev_info(cm->dev, "EVENT_HANDLE: Battery Fully Charged.\n");
uevent_notify(cm, default_event_names[CM_EVENT_BATT_FULL]);
}
/**
* battout_handler - Event handler for CM_EVENT_BATT_OUT
* @cm: the Charger Manager representing the battery.
*/
static void battout_handler(struct charger_manager *cm)
{
if (cm_suspended)
device_set_wakeup_capable(cm->dev, true);
if (!is_batt_present(cm)) {
dev_emerg(cm->dev, "Battery Pulled Out!\n");
uevent_notify(cm, default_event_names[CM_EVENT_BATT_OUT]);
} else {
uevent_notify(cm, "Battery Reinserted?");
}
}
/**
* misc_event_handler - Handler for other evnets
* @cm: the Charger Manager representing the battery.
* @type: the Charger Manager representing the battery.
*/
static void misc_event_handler(struct charger_manager *cm,
enum cm_event_types type)
{
if (cm_suspended)
device_set_wakeup_capable(cm->dev, true);
if (!delayed_work_pending(&cm_monitor_work) &&
is_polling_required(cm) && cm->desc->polling_interval_ms)
schedule_work(&setup_polling);
uevent_notify(cm, default_event_names[type]);
}
static int charger_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
......@@ -613,6 +819,21 @@ static bool cm_setup_timer(void)
mutex_lock(&cm_list_mtx);
list_for_each_entry(cm, &cm_list, entry) {
unsigned int fbchk_ms = 0;
/* fullbatt_vchk is required. setup timer for that */
if (cm->fullbatt_vchk_jiffies_at) {
fbchk_ms = jiffies_to_msecs(cm->fullbatt_vchk_jiffies_at
- jiffies);
if (time_is_before_eq_jiffies(
cm->fullbatt_vchk_jiffies_at) ||
msecs_to_jiffies(fbchk_ms) < CM_JIFFIES_SMALL) {
fullbatt_vchk(&cm->fullbatt_vchk_work.work);
fbchk_ms = 0;
}
}
CM_MIN_VALID(wakeup_ms, fbchk_ms);
/* Skip if polling is not required for this CM */
if (!is_polling_required(cm) && !cm->emergency_stop)
continue;
......@@ -672,6 +893,23 @@ static bool cm_setup_timer(void)
return false;
}
static void _cm_fbchk_in_suspend(struct charger_manager *cm)
{
unsigned long jiffy_now = jiffies;
if (!cm->fullbatt_vchk_jiffies_at)
return;
if (g_desc && g_desc->assume_timer_stops_in_suspend)
jiffy_now += msecs_to_jiffies(cm_suspend_duration_ms);
/* Execute now if it's going to be executed not too long after */
jiffy_now += CM_JIFFIES_SMALL;
if (time_after_eq(jiffy_now, cm->fullbatt_vchk_jiffies_at))
fullbatt_vchk(&cm->fullbatt_vchk_work.work);
}
/**
* cm_suspend_again - Determine whether suspend again or not
*
......@@ -693,6 +931,8 @@ bool cm_suspend_again(void)
ret = true;
mutex_lock(&cm_list_mtx);
list_for_each_entry(cm, &cm_list, entry) {
_cm_fbchk_in_suspend(cm);
if (cm->status_save_ext_pwr_inserted != is_ext_pwr_online(cm) ||
cm->status_save_batt != is_batt_present(cm)) {
ret = false;
......@@ -796,6 +1036,21 @@ static int charger_manager_probe(struct platform_device *pdev)
memcpy(cm->desc, desc, sizeof(struct charger_desc));
cm->last_temp_mC = INT_MIN; /* denotes "unmeasured, yet" */
/*
* The following two do not need to be errors.
* Users may intentionally ignore those two features.
*/
if (desc->fullbatt_uV == 0) {
dev_info(&pdev->dev, "Ignoring full-battery voltage threshold"
" as it is not supplied.");
}
if (!desc->fullbatt_vchkdrop_ms || !desc->fullbatt_vchkdrop_uV) {
dev_info(&pdev->dev, "Disabling full-battery voltage drop "
"checking mechanism as it is not supplied.");
desc->fullbatt_vchkdrop_ms = 0;
desc->fullbatt_vchkdrop_uV = 0;
}
if (!desc->charger_regulators || desc->num_charger_regulators < 1) {
ret = -EINVAL;
dev_err(&pdev->dev, "charger_regulators undefined.\n");
......@@ -903,6 +1158,8 @@ static int charger_manager_probe(struct platform_device *pdev)
cm->charger_psy.num_properties++;
}
INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk);
ret = power_supply_register(NULL, &cm->charger_psy);
if (ret) {
dev_err(&pdev->dev, "Cannot register charger-manager with"
......@@ -928,6 +1185,15 @@ static int charger_manager_probe(struct platform_device *pdev)
list_add(&cm->entry, &cm_list);
mutex_unlock(&cm_list_mtx);
/*
* Charger-manager is capable of waking up the systme from sleep
* when event is happend through cm_notify_event()
*/
device_init_wakeup(&pdev->dev, true);
device_set_wakeup_capable(&pdev->dev, false);
schedule_work(&setup_polling);
return 0;
err_chg_enable:
......@@ -958,9 +1224,17 @@ static int __devexit charger_manager_remove(struct platform_device *pdev)
list_del(&cm->entry);
mutex_unlock(&cm_list_mtx);
if (work_pending(&setup_polling))
cancel_work_sync(&setup_polling);
if (delayed_work_pending(&cm_monitor_work))
cancel_delayed_work_sync(&cm_monitor_work);
regulator_bulk_free(desc->num_charger_regulators,
desc->charger_regulators);
power_supply_unregister(&cm->charger_psy);
try_charger_enable(cm, false);
kfree(cm->charger_psy.properties);
kfree(cm->charger_stat);
kfree(cm->desc);
......@@ -975,6 +1249,18 @@ static const struct platform_device_id charger_manager_id[] = {
};
MODULE_DEVICE_TABLE(platform, charger_manager_id);
static int cm_suspend_noirq(struct device *dev)
{
int ret = 0;
if (device_may_wakeup(dev)) {
device_set_wakeup_capable(dev, false);
ret = -EAGAIN;
}
return ret;
}
static int cm_suspend_prepare(struct device *dev)
{
struct charger_manager *cm = dev_get_drvdata(dev);
......@@ -1000,6 +1286,8 @@ static int cm_suspend_prepare(struct device *dev)
cm_suspended = true;
}
if (delayed_work_pending(&cm->fullbatt_vchk_work))
cancel_delayed_work(&cm->fullbatt_vchk_work);
cm->status_save_ext_pwr_inserted = is_ext_pwr_online(cm);
cm->status_save_batt = is_batt_present(cm);
......@@ -1027,11 +1315,40 @@ static void cm_suspend_complete(struct device *dev)
cm_rtc_set = false;
}
/* Re-enqueue delayed work (fullbatt_vchk_work) */
if (cm->fullbatt_vchk_jiffies_at) {
unsigned long delay = 0;
unsigned long now = jiffies + CM_JIFFIES_SMALL;
if (time_after_eq(now, cm->fullbatt_vchk_jiffies_at)) {
delay = (unsigned long)((long)now
- (long)(cm->fullbatt_vchk_jiffies_at));
delay = jiffies_to_msecs(delay);
} else {
delay = 0;
}
/*
* Account for cm_suspend_duration_ms if
* assume_timer_stops_in_suspend is active
*/
if (g_desc && g_desc->assume_timer_stops_in_suspend) {
if (delay > cm_suspend_duration_ms)
delay -= cm_suspend_duration_ms;
else
delay = 0;
}
queue_delayed_work(cm_wq, &cm->fullbatt_vchk_work,
msecs_to_jiffies(delay));
}
device_set_wakeup_capable(cm->dev, false);
uevent_notify(cm, NULL);
}
static const struct dev_pm_ops charger_manager_pm = {
.prepare = cm_suspend_prepare,
.suspend_noirq = cm_suspend_noirq,
.complete = cm_suspend_complete,
};
......@@ -1048,16 +1365,91 @@ static struct platform_driver charger_manager_driver = {
static int __init charger_manager_init(void)
{
cm_wq = create_freezable_workqueue("charger_manager");
INIT_DELAYED_WORK(&cm_monitor_work, cm_monitor_poller);
return platform_driver_register(&charger_manager_driver);
}
late_initcall(charger_manager_init);
static void __exit charger_manager_cleanup(void)
{
destroy_workqueue(cm_wq);
cm_wq = NULL;
platform_driver_unregister(&charger_manager_driver);
}
module_exit(charger_manager_cleanup);
/**
* find_power_supply - find the associated power_supply of charger
* @cm: the Charger Manager representing the battery
* @psy: pointer to instance of charger's power_supply
*/
static bool find_power_supply(struct charger_manager *cm,
struct power_supply *psy)
{
int i;
bool found = false;
for (i = 0; cm->charger_stat[i]; i++) {
if (psy == cm->charger_stat[i]) {
found = true;
break;
}
}
return found;
}
/**
* cm_notify_event - charger driver notify Charger Manager of charger event
* @psy: pointer to instance of charger's power_supply
* @type: type of charger event
* @msg: optional message passed to uevent_notify fuction
*/
void cm_notify_event(struct power_supply *psy, enum cm_event_types type,
char *msg)
{
struct charger_manager *cm;
bool found_power_supply = false;
if (psy == NULL)
return;
mutex_lock(&cm_list_mtx);
list_for_each_entry(cm, &cm_list, entry) {
found_power_supply = find_power_supply(cm, psy);
if (found_power_supply)
break;
}
mutex_unlock(&cm_list_mtx);
if (!found_power_supply)
return;
switch (type) {
case CM_EVENT_BATT_FULL:
fullbatt_handler(cm);
break;
case CM_EVENT_BATT_OUT:
battout_handler(cm);
break;
case CM_EVENT_BATT_IN:
case CM_EVENT_EXT_PWR_IN_OUT ... CM_EVENT_CHG_START_STOP:
misc_event_handler(cm, type);
break;
case CM_EVENT_UNKNOWN:
case CM_EVENT_OTHERS:
uevent_notify(cm, msg ? msg : default_event_names[type]);
break;
default:
dev_err(cm->dev, "%s type not specified.\n", __func__);
break;
}
}
EXPORT_SYMBOL_GPL(cm_notify_event);
MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
MODULE_DESCRIPTION("Charger Manager");
MODULE_LICENSE("GPL");
......@@ -643,9 +643,7 @@ static ssize_t ds2781_read_param_eeprom_bin(struct file *filp,
struct power_supply *psy = to_power_supply(dev);
struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
count = min_t(loff_t, count,
DS2781_EEPROM_BLOCK1_END -
DS2781_EEPROM_BLOCK1_START + 1 - off);
count = min_t(loff_t, count, DS2781_PARAM_EEPROM_SIZE - off);
return ds2781_read_block(dev_info, buf,
DS2781_EEPROM_BLOCK1_START + off, count);
......@@ -661,9 +659,7 @@ static ssize_t ds2781_write_param_eeprom_bin(struct file *filp,
struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
int ret;
count = min_t(loff_t, count,
DS2781_EEPROM_BLOCK1_END -
DS2781_EEPROM_BLOCK1_START + 1 - off);
count = min_t(loff_t, count, DS2781_PARAM_EEPROM_SIZE - off);
ret = ds2781_write(dev_info, buf,
DS2781_EEPROM_BLOCK1_START + off, count);
......@@ -682,7 +678,7 @@ static struct bin_attribute ds2781_param_eeprom_bin_attr = {
.name = "param_eeprom",
.mode = S_IRUGO | S_IWUSR,
},
.size = DS2781_EEPROM_BLOCK1_END - DS2781_EEPROM_BLOCK1_START + 1,
.size = DS2781_PARAM_EEPROM_SIZE,
.read = ds2781_read_param_eeprom_bin,
.write = ds2781_write_param_eeprom_bin,
};
......@@ -696,9 +692,7 @@ static ssize_t ds2781_read_user_eeprom_bin(struct file *filp,
struct power_supply *psy = to_power_supply(dev);
struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
count = min_t(loff_t, count,
DS2781_EEPROM_BLOCK0_END -
DS2781_EEPROM_BLOCK0_START + 1 - off);
count = min_t(loff_t, count, DS2781_USER_EEPROM_SIZE - off);
return ds2781_read_block(dev_info, buf,
DS2781_EEPROM_BLOCK0_START + off, count);
......@@ -715,9 +709,7 @@ static ssize_t ds2781_write_user_eeprom_bin(struct file *filp,
struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
int ret;
count = min_t(loff_t, count,
DS2781_EEPROM_BLOCK0_END -
DS2781_EEPROM_BLOCK0_START + 1 - off);
count = min_t(loff_t, count, DS2781_USER_EEPROM_SIZE - off);
ret = ds2781_write(dev_info, buf,
DS2781_EEPROM_BLOCK0_START + off, count);
......@@ -736,7 +728,7 @@ static struct bin_attribute ds2781_user_eeprom_bin_attr = {
.name = "user_eeprom",
.mode = S_IRUGO | S_IWUSR,
},
.size = DS2781_EEPROM_BLOCK0_END - DS2781_EEPROM_BLOCK0_START + 1,
.size = DS2781_USER_EEPROM_SIZE,
.read = ds2781_read_user_eeprom_bin,
.write = ds2781_write_user_eeprom_bin,
};
......
......@@ -474,13 +474,13 @@ static int __devinit isp1704_charger_probe(struct platform_device *pdev)
fail2:
power_supply_unregister(&isp->psy);
fail1:
isp1704_charger_set_power(isp, 0);
usb_put_transceiver(isp->phy);
fail0:
kfree(isp);
dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret);
isp1704_charger_set_power(isp, 0);
return ret;
}
......
......@@ -28,6 +28,7 @@
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/pm.h>
#include <linux/mod_devicetable.h>
#include <linux/power_supply.h>
#include <linux/power/max17042_battery.h>
......@@ -61,9 +62,13 @@
#define dP_ACC_100 0x1900
#define dP_ACC_200 0x3200
#define MAX17042_IC_VERSION 0x0092
#define MAX17047_IC_VERSION 0x00AC /* same for max17050 */
struct max17042_chip {
struct i2c_client *client;
struct power_supply battery;
enum max170xx_chip_type chip_type;
struct max17042_platform_data *pdata;
struct work_struct work;
int init_complete;
......@@ -105,6 +110,7 @@ static enum power_supply_property max17042_battery_props[] = {
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_AVG,
POWER_SUPPLY_PROP_VOLTAGE_OCV,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_TEMP,
......@@ -150,7 +156,10 @@ static int max17042_get_property(struct power_supply *psy,
val->intval *= 20000; /* Units of LSB = 20mV */
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
ret = max17042_read_reg(chip->client, MAX17042_V_empty);
if (chip->chip_type == MAX17042)
ret = max17042_read_reg(chip->client, MAX17042_V_empty);
else
ret = max17042_read_reg(chip->client, MAX17047_V_empty);
if (ret < 0)
return ret;
......@@ -169,6 +178,13 @@ static int max17042_get_property(struct power_supply *psy,
if (ret < 0)
return ret;
val->intval = ret * 625 / 8;
break;
case POWER_SUPPLY_PROP_VOLTAGE_OCV:
ret = max17042_read_reg(chip->client, MAX17042_OCVInternal);
if (ret < 0)
return ret;
val->intval = ret * 625 / 8;
break;
case POWER_SUPPLY_PROP_CAPACITY:
......@@ -325,11 +341,10 @@ static inline int max17042_model_data_compare(struct max17042_chip *chip,
static int max17042_init_model(struct max17042_chip *chip)
{
int ret;
int table_size =
sizeof(chip->pdata->config_data->cell_char_tbl)/sizeof(u16);
int table_size = ARRAY_SIZE(chip->pdata->config_data->cell_char_tbl);
u16 *temp_data;
temp_data = kzalloc(table_size, GFP_KERNEL);
temp_data = kcalloc(table_size, sizeof(*temp_data), GFP_KERNEL);
if (!temp_data)
return -ENOMEM;
......@@ -354,12 +369,11 @@ static int max17042_init_model(struct max17042_chip *chip)
static int max17042_verify_model_lock(struct max17042_chip *chip)
{
int i;
int table_size =
sizeof(chip->pdata->config_data->cell_char_tbl);
int table_size = ARRAY_SIZE(chip->pdata->config_data->cell_char_tbl);
u16 *temp_data;
int ret = 0;
temp_data = kzalloc(table_size, GFP_KERNEL);
temp_data = kcalloc(table_size, sizeof(*temp_data), GFP_KERNEL);
if (!temp_data)
return -ENOMEM;
......@@ -382,6 +396,9 @@ static void max17042_write_config_regs(struct max17042_chip *chip)
max17042_write_reg(chip->client, MAX17042_FilterCFG,
config->filter_cfg);
max17042_write_reg(chip->client, MAX17042_RelaxCFG, config->relax_cfg);
if (chip->chip_type == MAX17047)
max17042_write_reg(chip->client, MAX17047_FullSOCThr,
config->full_soc_thresh);
}
static void max17042_write_custom_regs(struct max17042_chip *chip)
......@@ -392,12 +409,23 @@ static void max17042_write_custom_regs(struct max17042_chip *chip)
config->rcomp0);
max17042_write_verify_reg(chip->client, MAX17042_TempCo,
config->tcompc0);
max17042_write_reg(chip->client, MAX17042_EmptyTempCo,
config->empty_tempco);
max17042_write_verify_reg(chip->client, MAX17042_K_empty0,
config->kempty0);
max17042_write_verify_reg(chip->client, MAX17042_ICHGTerm,
config->ichgt_term);
if (chip->chip_type == MAX17042) {
max17042_write_reg(chip->client, MAX17042_EmptyTempCo,
config->empty_tempco);
max17042_write_verify_reg(chip->client, MAX17042_K_empty0,
config->kempty0);
} else {
max17042_write_verify_reg(chip->client, MAX17047_QRTbl00,
config->qrtbl00);
max17042_write_verify_reg(chip->client, MAX17047_QRTbl10,
config->qrtbl10);
max17042_write_verify_reg(chip->client, MAX17047_QRTbl20,
config->qrtbl20);
max17042_write_verify_reg(chip->client, MAX17047_QRTbl30,
config->qrtbl30);
}
}
static void max17042_update_capacity_regs(struct max17042_chip *chip)
......@@ -453,6 +481,8 @@ static void max17042_load_new_capacity_params(struct max17042_chip *chip)
config->design_cap);
max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom,
config->fullcapnom);
/* Update SOC register with new SOC */
max17042_write_reg(chip->client, MAX17042_RepSOC, vfSoc);
}
/*
......@@ -489,20 +519,28 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
max17042_override_por(client, MAX17042_FullCAP, config->fullcap);
max17042_override_por(client, MAX17042_FullCAPNom, config->fullcapnom);
max17042_override_por(client, MAX17042_SOC_empty, config->socempty);
if (chip->chip_type == MAX17042)
max17042_override_por(client, MAX17042_SOC_empty,
config->socempty);
max17042_override_por(client, MAX17042_LAvg_empty, config->lavg_empty);
max17042_override_por(client, MAX17042_dQacc, config->dqacc);
max17042_override_por(client, MAX17042_dPacc, config->dpacc);
max17042_override_por(client, MAX17042_V_empty, config->vempty);
if (chip->chip_type == MAX17042)
max17042_override_por(client, MAX17042_V_empty, config->vempty);
else
max17042_override_por(client, MAX17047_V_empty, config->vempty);
max17042_override_por(client, MAX17042_TempNom, config->temp_nom);
max17042_override_por(client, MAX17042_TempLim, config->temp_lim);
max17042_override_por(client, MAX17042_FCTC, config->fctc);
max17042_override_por(client, MAX17042_RCOMP0, config->rcomp0);
max17042_override_por(client, MAX17042_TempCo, config->tcompc0);
max17042_override_por(client, MAX17042_EmptyTempCo,
config->empty_tempco);
max17042_override_por(client, MAX17042_K_empty0, config->kempty0);
if (chip->chip_type) {
max17042_override_por(client, MAX17042_EmptyTempCo,
config->empty_tempco);
max17042_override_por(client, MAX17042_K_empty0,
config->kempty0);
}
}
static int max17042_init_chip(struct max17042_chip *chip)
......@@ -659,7 +697,19 @@ static int __devinit max17042_probe(struct i2c_client *client,
i2c_set_clientdata(client, chip);
chip->battery.name = "max17042_battery";
ret = max17042_read_reg(chip->client, MAX17042_DevName);
if (ret == MAX17042_IC_VERSION) {
dev_dbg(&client->dev, "chip type max17042 detected\n");
chip->chip_type = MAX17042;
} else if (ret == MAX17047_IC_VERSION) {
dev_dbg(&client->dev, "chip type max17047/50 detected\n");
chip->chip_type = MAX17047;
} else {
dev_err(&client->dev, "device version mismatch: %x\n", ret);
return -EIO;
}
chip->battery.name = "max170xx_battery";
chip->battery.type = POWER_SUPPLY_TYPE_BATTERY;
chip->battery.get_property = max17042_get_property;
chip->battery.properties = max17042_battery_props;
......@@ -683,6 +733,12 @@ static int __devinit max17042_probe(struct i2c_client *client,
max17042_write_reg(client, MAX17042_LearnCFG, 0x0007);
}
ret = power_supply_register(&client->dev, &chip->battery);
if (ret) {
dev_err(&client->dev, "failed: power supply register\n");
return ret;
}
if (client->irq) {
ret = request_threaded_irq(client->irq, NULL,
max17042_thread_handler,
......@@ -693,13 +749,14 @@ static int __devinit max17042_probe(struct i2c_client *client,
reg |= CONFIG_ALRT_BIT_ENBL;
max17042_write_reg(client, MAX17042_CONFIG, reg);
max17042_set_soc_threshold(chip, 1);
} else
} else {
client->irq = 0;
dev_err(&client->dev, "%s(): cannot get IRQ\n",
__func__);
}
}
reg = max17042_read_reg(chip->client, MAX17042_STATUS);
if (reg & STATUS_POR_BIT) {
INIT_WORK(&chip->work, max17042_init_worker);
schedule_work(&chip->work);
......@@ -707,23 +764,65 @@ static int __devinit max17042_probe(struct i2c_client *client,
chip->init_complete = 1;
}
ret = power_supply_register(&client->dev, &chip->battery);
if (ret)
dev_err(&client->dev, "failed: power supply register\n");
return ret;
return 0;
}
static int __devexit max17042_remove(struct i2c_client *client)
{
struct max17042_chip *chip = i2c_get_clientdata(client);
if (client->irq)
free_irq(client->irq, chip);
power_supply_unregister(&chip->battery);
return 0;
}
#ifdef CONFIG_PM
static int max17042_suspend(struct device *dev)
{
struct max17042_chip *chip = dev_get_drvdata(dev);
/*
* disable the irq and enable irq_wake
* capability to the interrupt line.
*/
if (chip->client->irq) {
disable_irq(chip->client->irq);
enable_irq_wake(chip->client->irq);
}
return 0;
}
static int max17042_resume(struct device *dev)
{
struct max17042_chip *chip = dev_get_drvdata(dev);
if (chip->client->irq) {
disable_irq_wake(chip->client->irq);
enable_irq(chip->client->irq);
/* re-program the SOC thresholds to 1% change */
max17042_set_soc_threshold(chip, 1);
}
return 0;
}
static const struct dev_pm_ops max17042_pm_ops = {
.suspend = max17042_suspend,
.resume = max17042_resume,
};
#define MAX17042_PM_OPS (&max17042_pm_ops)
#else
#define MAX17042_PM_OPS NULL
#endif
#ifdef CONFIG_OF
static const struct of_device_id max17042_dt_match[] = {
{ .compatible = "maxim,max17042" },
{ .compatible = "maxim,max17047" },
{ .compatible = "maxim,max17050" },
{ },
};
MODULE_DEVICE_TABLE(of, max17042_dt_match);
......@@ -731,6 +830,8 @@ MODULE_DEVICE_TABLE(of, max17042_dt_match);
static const struct i2c_device_id max17042_id[] = {
{ "max17042", 0 },
{ "max17047", 1 },
{ "max17050", 2 },
{ }
};
MODULE_DEVICE_TABLE(i2c, max17042_id);
......@@ -739,6 +840,7 @@ static struct i2c_driver max17042_i2c_driver = {
.driver = {
.name = "max17042",
.of_match_table = of_match_ptr(max17042_dt_match),
.pm = MAX17042_PM_OPS,
},
.probe = max17042_probe,
.remove = __devexit_p(max17042_remove),
......
......@@ -146,6 +146,7 @@ static struct device_attribute power_supply_attrs[] = {
POWER_SUPPLY_ATTR(voltage_min_design),
POWER_SUPPLY_ATTR(voltage_now),
POWER_SUPPLY_ATTR(voltage_avg),
POWER_SUPPLY_ATTR(voltage_ocv),
POWER_SUPPLY_ATTR(current_max),
POWER_SUPPLY_ATTR(current_now),
POWER_SUPPLY_ATTR(current_avg),
......
......@@ -89,7 +89,7 @@ static const struct chip_data {
[REG_CURRENT] =
SBS_DATA(POWER_SUPPLY_PROP_CURRENT_NOW, 0x0A, -32768, 32767),
[REG_CAPACITY] =
SBS_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x0E, 0, 100),
SBS_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x0D, 0, 100),
[REG_REMAINING_CAPACITY] =
SBS_DATA(POWER_SUPPLY_PROP_ENERGY_NOW, 0x0F, 0, 65535),
[REG_REMAINING_CAPACITY_CHARGE] =
......
......@@ -11,7 +11,7 @@
* published by the Free Software Foundation.
*/
#include <linux/debugfs.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/kernel.h>
#include <linux/module.h>
......@@ -21,7 +21,7 @@
#include <linux/mutex.h>
#include <linux/power_supply.h>
#include <linux/power/smb347-charger.h>
#include <linux/seq_file.h>
#include <linux/regmap.h>
/*
* Configuration registers. These are mirrored to volatile RAM and can be
......@@ -39,6 +39,7 @@
#define CFG_CURRENT_LIMIT_DC_SHIFT 4
#define CFG_CURRENT_LIMIT_USB_MASK 0x0f
#define CFG_FLOAT_VOLTAGE 0x03
#define CFG_FLOAT_VOLTAGE_FLOAT_MASK 0x3f
#define CFG_FLOAT_VOLTAGE_THRESHOLD_MASK 0xc0
#define CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT 6
#define CFG_STAT 0x05
......@@ -113,29 +114,31 @@
#define STAT_C_CHARGER_ERROR BIT(6)
#define STAT_E 0x3f
#define SMB347_MAX_REGISTER 0x3f
/**
* struct smb347_charger - smb347 charger instance
* @lock: protects concurrent access to online variables
* @client: pointer to i2c client
* @dev: pointer to device
* @regmap: pointer to driver regmap
* @mains: power_supply instance for AC/DC power
* @usb: power_supply instance for USB power
* @battery: power_supply instance for battery
* @mains_online: is AC/DC input connected
* @usb_online: is USB input connected
* @charging_enabled: is charging enabled
* @dentry: for debugfs
* @pdata: pointer to platform data
*/
struct smb347_charger {
struct mutex lock;
struct i2c_client *client;
struct device *dev;
struct regmap *regmap;
struct power_supply mains;
struct power_supply usb;
struct power_supply battery;
bool mains_online;
bool usb_online;
bool charging_enabled;
struct dentry *dentry;
const struct smb347_charger_platform_data *pdata;
};
......@@ -193,14 +196,6 @@ static const unsigned int ccc_tbl[] = {
1200000,
};
/* Convert register value to current using lookup table */
static int hw_to_current(const unsigned int *tbl, size_t size, unsigned int val)
{
if (val >= size)
return -EINVAL;
return tbl[val];
}
/* Convert current to register value using lookup table */
static int current_to_hw(const unsigned int *tbl, size_t size, unsigned int val)
{
......@@ -212,43 +207,22 @@ static int current_to_hw(const unsigned int *tbl, size_t size, unsigned int val)
return i > 0 ? i - 1 : -EINVAL;
}
static int smb347_read(struct smb347_charger *smb, u8 reg)
{
int ret;
ret = i2c_smbus_read_byte_data(smb->client, reg);
if (ret < 0)
dev_warn(&smb->client->dev, "failed to read reg 0x%x: %d\n",
reg, ret);
return ret;
}
static int smb347_write(struct smb347_charger *smb, u8 reg, u8 val)
{
int ret;
ret = i2c_smbus_write_byte_data(smb->client, reg, val);
if (ret < 0)
dev_warn(&smb->client->dev, "failed to write reg 0x%x: %d\n",
reg, ret);
return ret;
}
/**
* smb347_update_status - updates the charging status
* smb347_update_ps_status - refreshes the power source status
* @smb: pointer to smb347 charger instance
*
* Function checks status of the charging and updates internal state
* accordingly. Returns %0 if there is no change in status, %1 if the
* status has changed and negative errno in case of failure.
* Function checks whether any power source is connected to the charger and
* updates internal state accordingly. If there is a change to previous state
* function returns %1, otherwise %0 and negative errno in case of errror.
*/
static int smb347_update_status(struct smb347_charger *smb)
static int smb347_update_ps_status(struct smb347_charger *smb)
{
bool usb = false;
bool dc = false;
unsigned int val;
int ret;
ret = smb347_read(smb, IRQSTAT_E);
ret = regmap_read(smb->regmap, IRQSTAT_E, &val);
if (ret < 0)
return ret;
......@@ -257,9 +231,9 @@ static int smb347_update_status(struct smb347_charger *smb)
* platform data _and_ whether corresponding undervoltage is set.
*/
if (smb->pdata->use_mains)
dc = !(ret & IRQSTAT_E_DCIN_UV_STAT);
dc = !(val & IRQSTAT_E_DCIN_UV_STAT);
if (smb->pdata->use_usb)
usb = !(ret & IRQSTAT_E_USBIN_UV_STAT);
usb = !(val & IRQSTAT_E_USBIN_UV_STAT);
mutex_lock(&smb->lock);
ret = smb->mains_online != dc || smb->usb_online != usb;
......@@ -271,15 +245,15 @@ static int smb347_update_status(struct smb347_charger *smb)
}
/*
* smb347_is_online - returns whether input power source is connected
* smb347_is_ps_online - returns whether input power source is connected
* @smb: pointer to smb347 charger instance
*
* Returns %true if input power source is connected. Note that this is
* dependent on what platform has configured for usable power sources. For
* example if USB is disabled, this will return %false even if the USB
* cable is connected.
* example if USB is disabled, this will return %false even if the USB cable
* is connected.
*/
static bool smb347_is_online(struct smb347_charger *smb)
static bool smb347_is_ps_online(struct smb347_charger *smb)
{
bool ret;
......@@ -299,16 +273,17 @@ static bool smb347_is_online(struct smb347_charger *smb)
*/
static int smb347_charging_status(struct smb347_charger *smb)
{
unsigned int val;
int ret;
if (!smb347_is_online(smb))
if (!smb347_is_ps_online(smb))
return 0;
ret = smb347_read(smb, STAT_C);
ret = regmap_read(smb->regmap, STAT_C, &val);
if (ret < 0)
return 0;
return (ret & STAT_C_CHG_MASK) >> STAT_C_CHG_SHIFT;
return (val & STAT_C_CHG_MASK) >> STAT_C_CHG_SHIFT;
}
static int smb347_charging_set(struct smb347_charger *smb, bool enable)
......@@ -316,27 +291,17 @@ static int smb347_charging_set(struct smb347_charger *smb, bool enable)
int ret = 0;
if (smb->pdata->enable_control != SMB347_CHG_ENABLE_SW) {
dev_dbg(&smb->client->dev,
"charging enable/disable in SW disabled\n");
dev_dbg(smb->dev, "charging enable/disable in SW disabled\n");
return 0;
}
mutex_lock(&smb->lock);
if (smb->charging_enabled != enable) {
ret = smb347_read(smb, CMD_A);
if (ret < 0)
goto out;
smb->charging_enabled = enable;
if (enable)
ret |= CMD_A_CHG_ENABLED;
else
ret &= ~CMD_A_CHG_ENABLED;
ret = smb347_write(smb, CMD_A, ret);
ret = regmap_update_bits(smb->regmap, CMD_A, CMD_A_CHG_ENABLED,
enable ? CMD_A_CHG_ENABLED : 0);
if (!ret)
smb->charging_enabled = enable;
}
out:
mutex_unlock(&smb->lock);
return ret;
}
......@@ -351,7 +316,7 @@ static inline int smb347_charging_disable(struct smb347_charger *smb)
return smb347_charging_set(smb, false);
}
static int smb347_update_online(struct smb347_charger *smb)
static int smb347_start_stop_charging(struct smb347_charger *smb)
{
int ret;
......@@ -360,16 +325,14 @@ static int smb347_update_online(struct smb347_charger *smb)
* disable or enable the charging. We do it manually because it
* depends on how the platform has configured the valid inputs.
*/
if (smb347_is_online(smb)) {
if (smb347_is_ps_online(smb)) {
ret = smb347_charging_enable(smb);
if (ret < 0)
dev_err(&smb->client->dev,
"failed to enable charging\n");
dev_err(smb->dev, "failed to enable charging\n");
} else {
ret = smb347_charging_disable(smb);
if (ret < 0)
dev_err(&smb->client->dev,
"failed to disable charging\n");
dev_err(smb->dev, "failed to disable charging\n");
}
return ret;
......@@ -377,112 +340,120 @@ static int smb347_update_online(struct smb347_charger *smb)
static int smb347_set_charge_current(struct smb347_charger *smb)
{
int ret, val;
ret = smb347_read(smb, CFG_CHARGE_CURRENT);
if (ret < 0)
return ret;
int ret;
if (smb->pdata->max_charge_current) {
val = current_to_hw(fcc_tbl, ARRAY_SIZE(fcc_tbl),
ret = current_to_hw(fcc_tbl, ARRAY_SIZE(fcc_tbl),
smb->pdata->max_charge_current);
if (val < 0)
return val;
if (ret < 0)
return ret;
ret &= ~CFG_CHARGE_CURRENT_FCC_MASK;
ret |= val << CFG_CHARGE_CURRENT_FCC_SHIFT;
ret = regmap_update_bits(smb->regmap, CFG_CHARGE_CURRENT,
CFG_CHARGE_CURRENT_FCC_MASK,
ret << CFG_CHARGE_CURRENT_FCC_SHIFT);
if (ret < 0)
return ret;
}
if (smb->pdata->pre_charge_current) {
val = current_to_hw(pcc_tbl, ARRAY_SIZE(pcc_tbl),
ret = current_to_hw(pcc_tbl, ARRAY_SIZE(pcc_tbl),
smb->pdata->pre_charge_current);
if (val < 0)
return val;
if (ret < 0)
return ret;
ret &= ~CFG_CHARGE_CURRENT_PCC_MASK;
ret |= val << CFG_CHARGE_CURRENT_PCC_SHIFT;
ret = regmap_update_bits(smb->regmap, CFG_CHARGE_CURRENT,
CFG_CHARGE_CURRENT_PCC_MASK,
ret << CFG_CHARGE_CURRENT_PCC_SHIFT);
if (ret < 0)
return ret;
}
if (smb->pdata->termination_current) {
val = current_to_hw(tc_tbl, ARRAY_SIZE(tc_tbl),
ret = current_to_hw(tc_tbl, ARRAY_SIZE(tc_tbl),
smb->pdata->termination_current);
if (val < 0)
return val;
if (ret < 0)
return ret;
ret &= ~CFG_CHARGE_CURRENT_TC_MASK;
ret |= val;
ret = regmap_update_bits(smb->regmap, CFG_CHARGE_CURRENT,
CFG_CHARGE_CURRENT_TC_MASK, ret);
if (ret < 0)
return ret;
}
return smb347_write(smb, CFG_CHARGE_CURRENT, ret);
return 0;
}
static int smb347_set_current_limits(struct smb347_charger *smb)
{
int ret, val;
ret = smb347_read(smb, CFG_CURRENT_LIMIT);
if (ret < 0)
return ret;
int ret;
if (smb->pdata->mains_current_limit) {
val = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl),
ret = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl),
smb->pdata->mains_current_limit);
if (val < 0)
return val;
if (ret < 0)
return ret;
ret &= ~CFG_CURRENT_LIMIT_DC_MASK;
ret |= val << CFG_CURRENT_LIMIT_DC_SHIFT;
ret = regmap_update_bits(smb->regmap, CFG_CURRENT_LIMIT,
CFG_CURRENT_LIMIT_DC_MASK,
ret << CFG_CURRENT_LIMIT_DC_SHIFT);
if (ret < 0)
return ret;
}
if (smb->pdata->usb_hc_current_limit) {
val = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl),
ret = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl),
smb->pdata->usb_hc_current_limit);
if (val < 0)
return val;
if (ret < 0)
return ret;
ret &= ~CFG_CURRENT_LIMIT_USB_MASK;
ret |= val;
ret = regmap_update_bits(smb->regmap, CFG_CURRENT_LIMIT,
CFG_CURRENT_LIMIT_USB_MASK, ret);
if (ret < 0)
return ret;
}
return smb347_write(smb, CFG_CURRENT_LIMIT, ret);
return 0;
}
static int smb347_set_voltage_limits(struct smb347_charger *smb)
{
int ret, val;
ret = smb347_read(smb, CFG_FLOAT_VOLTAGE);
if (ret < 0)
return ret;
int ret;
if (smb->pdata->pre_to_fast_voltage) {
val = smb->pdata->pre_to_fast_voltage;
ret = smb->pdata->pre_to_fast_voltage;
/* uV */
val = clamp_val(val, 2400000, 3000000) - 2400000;
val /= 200000;
ret = clamp_val(ret, 2400000, 3000000) - 2400000;
ret /= 200000;
ret &= ~CFG_FLOAT_VOLTAGE_THRESHOLD_MASK;
ret |= val << CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT;
ret = regmap_update_bits(smb->regmap, CFG_FLOAT_VOLTAGE,
CFG_FLOAT_VOLTAGE_THRESHOLD_MASK,
ret << CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT);
if (ret < 0)
return ret;
}
if (smb->pdata->max_charge_voltage) {
val = smb->pdata->max_charge_voltage;
ret = smb->pdata->max_charge_voltage;
/* uV */
val = clamp_val(val, 3500000, 4500000) - 3500000;
val /= 20000;
ret = clamp_val(ret, 3500000, 4500000) - 3500000;
ret /= 20000;
ret |= val;
ret = regmap_update_bits(smb->regmap, CFG_FLOAT_VOLTAGE,
CFG_FLOAT_VOLTAGE_FLOAT_MASK, ret);
if (ret < 0)
return ret;
}
return smb347_write(smb, CFG_FLOAT_VOLTAGE, ret);
return 0;
}
static int smb347_set_temp_limits(struct smb347_charger *smb)
{
bool enable_therm_monitor = false;
int ret, val;
int ret = 0;
int val;
if (smb->pdata->chip_temp_threshold) {
val = smb->pdata->chip_temp_threshold;
......@@ -491,22 +462,13 @@ static int smb347_set_temp_limits(struct smb347_charger *smb)
val = clamp_val(val, 100, 130) - 100;
val /= 10;
ret = smb347_read(smb, CFG_OTG);
if (ret < 0)
return ret;
ret &= ~CFG_OTG_TEMP_THRESHOLD_MASK;
ret |= val << CFG_OTG_TEMP_THRESHOLD_SHIFT;
ret = smb347_write(smb, CFG_OTG, ret);
ret = regmap_update_bits(smb->regmap, CFG_OTG,
CFG_OTG_TEMP_THRESHOLD_MASK,
val << CFG_OTG_TEMP_THRESHOLD_SHIFT);
if (ret < 0)
return ret;
}
ret = smb347_read(smb, CFG_TEMP_LIMIT);
if (ret < 0)
return ret;
if (smb->pdata->soft_cold_temp_limit != SMB347_TEMP_USE_DEFAULT) {
val = smb->pdata->soft_cold_temp_limit;
......@@ -515,8 +477,11 @@ static int smb347_set_temp_limits(struct smb347_charger *smb)
/* this goes from higher to lower so invert the value */
val = ~val & 0x3;
ret &= ~CFG_TEMP_LIMIT_SOFT_COLD_MASK;
ret |= val << CFG_TEMP_LIMIT_SOFT_COLD_SHIFT;
ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT,
CFG_TEMP_LIMIT_SOFT_COLD_MASK,
val << CFG_TEMP_LIMIT_SOFT_COLD_SHIFT);
if (ret < 0)
return ret;
enable_therm_monitor = true;
}
......@@ -527,8 +492,11 @@ static int smb347_set_temp_limits(struct smb347_charger *smb)
val = clamp_val(val, 40, 55) - 40;
val /= 5;
ret &= ~CFG_TEMP_LIMIT_SOFT_HOT_MASK;
ret |= val << CFG_TEMP_LIMIT_SOFT_HOT_SHIFT;
ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT,
CFG_TEMP_LIMIT_SOFT_HOT_MASK,
val << CFG_TEMP_LIMIT_SOFT_HOT_SHIFT);
if (ret < 0)
return ret;
enable_therm_monitor = true;
}
......@@ -541,8 +509,11 @@ static int smb347_set_temp_limits(struct smb347_charger *smb)
/* this goes from higher to lower so invert the value */
val = ~val & 0x3;
ret &= ~CFG_TEMP_LIMIT_HARD_COLD_MASK;
ret |= val << CFG_TEMP_LIMIT_HARD_COLD_SHIFT;
ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT,
CFG_TEMP_LIMIT_HARD_COLD_MASK,
val << CFG_TEMP_LIMIT_HARD_COLD_SHIFT);
if (ret < 0)
return ret;
enable_therm_monitor = true;
}
......@@ -553,16 +524,15 @@ static int smb347_set_temp_limits(struct smb347_charger *smb)
val = clamp_val(val, 50, 65) - 50;
val /= 5;
ret &= ~CFG_TEMP_LIMIT_HARD_HOT_MASK;
ret |= val << CFG_TEMP_LIMIT_HARD_HOT_SHIFT;
ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT,
CFG_TEMP_LIMIT_HARD_HOT_MASK,
val << CFG_TEMP_LIMIT_HARD_HOT_SHIFT);
if (ret < 0)
return ret;
enable_therm_monitor = true;
}
ret = smb347_write(smb, CFG_TEMP_LIMIT, ret);
if (ret < 0)
return ret;
/*
* If any of the temperature limits are set, we also enable the
* thermistor monitoring.
......@@ -574,25 +544,15 @@ static int smb347_set_temp_limits(struct smb347_charger *smb)
* depending on the configuration.
*/
if (enable_therm_monitor) {
ret = smb347_read(smb, CFG_THERM);
if (ret < 0)
return ret;
ret &= ~CFG_THERM_MONITOR_DISABLED;
ret = smb347_write(smb, CFG_THERM, ret);
ret = regmap_update_bits(smb->regmap, CFG_THERM,
CFG_THERM_MONITOR_DISABLED, 0);
if (ret < 0)
return ret;
}
if (smb->pdata->suspend_on_hard_temp_limit) {
ret = smb347_read(smb, CFG_SYSOK);
if (ret < 0)
return ret;
ret &= ~CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED;
ret = smb347_write(smb, CFG_SYSOK, ret);
ret = regmap_update_bits(smb->regmap, CFG_SYSOK,
CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED, 0);
if (ret < 0)
return ret;
}
......@@ -601,17 +561,15 @@ static int smb347_set_temp_limits(struct smb347_charger *smb)
SMB347_SOFT_TEMP_COMPENSATE_DEFAULT) {
val = smb->pdata->soft_temp_limit_compensation & 0x3;
ret = smb347_read(smb, CFG_THERM);
ret = regmap_update_bits(smb->regmap, CFG_THERM,
CFG_THERM_SOFT_HOT_COMPENSATION_MASK,
val << CFG_THERM_SOFT_HOT_COMPENSATION_SHIFT);
if (ret < 0)
return ret;
ret &= ~CFG_THERM_SOFT_HOT_COMPENSATION_MASK;
ret |= val << CFG_THERM_SOFT_HOT_COMPENSATION_SHIFT;
ret &= ~CFG_THERM_SOFT_COLD_COMPENSATION_MASK;
ret |= val << CFG_THERM_SOFT_COLD_COMPENSATION_SHIFT;
ret = smb347_write(smb, CFG_THERM, ret);
ret = regmap_update_bits(smb->regmap, CFG_THERM,
CFG_THERM_SOFT_COLD_COMPENSATION_MASK,
val << CFG_THERM_SOFT_COLD_COMPENSATION_SHIFT);
if (ret < 0)
return ret;
}
......@@ -622,14 +580,9 @@ static int smb347_set_temp_limits(struct smb347_charger *smb)
if (val < 0)
return val;
ret = smb347_read(smb, CFG_OTG);
if (ret < 0)
return ret;
ret &= ~CFG_OTG_CC_COMPENSATION_MASK;
ret |= (val & 0x3) << CFG_OTG_CC_COMPENSATION_SHIFT;
ret = smb347_write(smb, CFG_OTG, ret);
ret = regmap_update_bits(smb->regmap, CFG_OTG,
CFG_OTG_CC_COMPENSATION_MASK,
(val & 0x3) << CFG_OTG_CC_COMPENSATION_SHIFT);
if (ret < 0)
return ret;
}
......@@ -648,22 +601,13 @@ static int smb347_set_temp_limits(struct smb347_charger *smb)
*/
static int smb347_set_writable(struct smb347_charger *smb, bool writable)
{
int ret;
ret = smb347_read(smb, CMD_A);
if (ret < 0)
return ret;
if (writable)
ret |= CMD_A_ALLOW_WRITE;
else
ret &= ~CMD_A_ALLOW_WRITE;
return smb347_write(smb, CMD_A, ret);
return regmap_update_bits(smb->regmap, CMD_A, CMD_A_ALLOW_WRITE,
writable ? CMD_A_ALLOW_WRITE : 0);
}
static int smb347_hw_init(struct smb347_charger *smb)
{
unsigned int val;
int ret;
ret = smb347_set_writable(smb, true);
......@@ -692,34 +636,19 @@ static int smb347_hw_init(struct smb347_charger *smb)
/* If USB charging is disabled we put the USB in suspend mode */
if (!smb->pdata->use_usb) {
ret = smb347_read(smb, CMD_A);
if (ret < 0)
goto fail;
ret |= CMD_A_SUSPEND_ENABLED;
ret = smb347_write(smb, CMD_A, ret);
ret = regmap_update_bits(smb->regmap, CMD_A,
CMD_A_SUSPEND_ENABLED,
CMD_A_SUSPEND_ENABLED);
if (ret < 0)
goto fail;
}
ret = smb347_read(smb, CFG_OTHER);
if (ret < 0)
goto fail;
/*
* If configured by platform data, we enable hardware Auto-OTG
* support for driving VBUS. Otherwise we disable it.
*/
ret &= ~CFG_OTHER_RID_MASK;
if (smb->pdata->use_usb_otg)
ret |= CFG_OTHER_RID_ENABLED_AUTO_OTG;
ret = smb347_write(smb, CFG_OTHER, ret);
if (ret < 0)
goto fail;
ret = smb347_read(smb, CFG_PIN);
ret = regmap_update_bits(smb->regmap, CFG_OTHER, CFG_OTHER_RID_MASK,
smb->pdata->use_usb_otg ? CFG_OTHER_RID_ENABLED_AUTO_OTG : 0);
if (ret < 0)
goto fail;
......@@ -728,32 +657,33 @@ static int smb347_hw_init(struct smb347_charger *smb)
* command register unless pin control is specified in the platform
* data.
*/
ret &= ~CFG_PIN_EN_CTRL_MASK;
switch (smb->pdata->enable_control) {
case SMB347_CHG_ENABLE_SW:
/* Do nothing, 0 means i2c control */
break;
case SMB347_CHG_ENABLE_PIN_ACTIVE_LOW:
ret |= CFG_PIN_EN_CTRL_ACTIVE_LOW;
val = CFG_PIN_EN_CTRL_ACTIVE_LOW;
break;
case SMB347_CHG_ENABLE_PIN_ACTIVE_HIGH:
ret |= CFG_PIN_EN_CTRL_ACTIVE_HIGH;
val = CFG_PIN_EN_CTRL_ACTIVE_HIGH;
break;
default:
val = 0;
break;
}
/* Disable Automatic Power Source Detection (APSD) interrupt. */
ret &= ~CFG_PIN_EN_APSD_IRQ;
ret = regmap_update_bits(smb->regmap, CFG_PIN, CFG_PIN_EN_CTRL_MASK,
val);
if (ret < 0)
goto fail;
ret = smb347_write(smb, CFG_PIN, ret);
/* Disable Automatic Power Source Detection (APSD) interrupt. */
ret = regmap_update_bits(smb->regmap, CFG_PIN, CFG_PIN_EN_APSD_IRQ, 0);
if (ret < 0)
goto fail;
ret = smb347_update_status(smb);
ret = smb347_update_ps_status(smb);
if (ret < 0)
goto fail;
ret = smb347_update_online(smb);
ret = smb347_start_stop_charging(smb);
fail:
smb347_set_writable(smb, false);
......@@ -763,24 +693,25 @@ static int smb347_hw_init(struct smb347_charger *smb)
static irqreturn_t smb347_interrupt(int irq, void *data)
{
struct smb347_charger *smb = data;
int stat_c, irqstat_e, irqstat_c;
irqreturn_t ret = IRQ_NONE;
unsigned int stat_c, irqstat_e, irqstat_c;
bool handled = false;
int ret;
stat_c = smb347_read(smb, STAT_C);
if (stat_c < 0) {
dev_warn(&smb->client->dev, "reading STAT_C failed\n");
ret = regmap_read(smb->regmap, STAT_C, &stat_c);
if (ret < 0) {
dev_warn(smb->dev, "reading STAT_C failed\n");
return IRQ_NONE;
}
irqstat_c = smb347_read(smb, IRQSTAT_C);
if (irqstat_c < 0) {
dev_warn(&smb->client->dev, "reading IRQSTAT_C failed\n");
ret = regmap_read(smb->regmap, IRQSTAT_C, &irqstat_c);
if (ret < 0) {
dev_warn(smb->dev, "reading IRQSTAT_C failed\n");
return IRQ_NONE;
}
irqstat_e = smb347_read(smb, IRQSTAT_E);
if (irqstat_e < 0) {
dev_warn(&smb->client->dev, "reading IRQSTAT_E failed\n");
ret = regmap_read(smb->regmap, IRQSTAT_E, &irqstat_e);
if (ret < 0) {
dev_warn(smb->dev, "reading IRQSTAT_E failed\n");
return IRQ_NONE;
}
......@@ -789,13 +720,11 @@ static irqreturn_t smb347_interrupt(int irq, void *data)
* disable charging.
*/
if (stat_c & STAT_C_CHARGER_ERROR) {
dev_err(&smb->client->dev,
"error in charger, disabling charging\n");
dev_err(smb->dev, "error in charger, disabling charging\n");
smb347_charging_disable(smb);
power_supply_changed(&smb->battery);
ret = IRQ_HANDLED;
handled = true;
}
/*
......@@ -806,7 +735,7 @@ static irqreturn_t smb347_interrupt(int irq, void *data)
if (irqstat_c & (IRQSTAT_C_TERMINATION_IRQ | IRQSTAT_C_TAPER_IRQ)) {
if (irqstat_c & IRQSTAT_C_TERMINATION_STAT)
power_supply_changed(&smb->battery);
ret = IRQ_HANDLED;
handled = true;
}
/*
......@@ -814,15 +743,17 @@ static irqreturn_t smb347_interrupt(int irq, void *data)
* was connected or disconnected.
*/
if (irqstat_e & (IRQSTAT_E_USBIN_UV_IRQ | IRQSTAT_E_DCIN_UV_IRQ)) {
if (smb347_update_status(smb) > 0) {
smb347_update_online(smb);
power_supply_changed(&smb->mains);
power_supply_changed(&smb->usb);
if (smb347_update_ps_status(smb) > 0) {
smb347_start_stop_charging(smb);
if (smb->pdata->use_mains)
power_supply_changed(&smb->mains);
if (smb->pdata->use_usb)
power_supply_changed(&smb->usb);
}
ret = IRQ_HANDLED;
handled = true;
}
return ret;
return handled ? IRQ_HANDLED : IRQ_NONE;
}
static int smb347_irq_set(struct smb347_charger *smb, bool enable)
......@@ -839,41 +770,18 @@ static int smb347_irq_set(struct smb347_charger *smb, bool enable)
* - termination current reached
* - charger error
*/
if (enable) {
ret = smb347_write(smb, CFG_FAULT_IRQ, CFG_FAULT_IRQ_DCIN_UV);
if (ret < 0)
goto fail;
ret = smb347_write(smb, CFG_STATUS_IRQ,
CFG_STATUS_IRQ_TERMINATION_OR_TAPER);
if (ret < 0)
goto fail;
ret = smb347_read(smb, CFG_PIN);
if (ret < 0)
goto fail;
ret |= CFG_PIN_EN_CHARGER_ERROR;
ret = smb347_write(smb, CFG_PIN, ret);
} else {
ret = smb347_write(smb, CFG_FAULT_IRQ, 0);
if (ret < 0)
goto fail;
ret = smb347_write(smb, CFG_STATUS_IRQ, 0);
if (ret < 0)
goto fail;
ret = smb347_read(smb, CFG_PIN);
if (ret < 0)
goto fail;
ret &= ~CFG_PIN_EN_CHARGER_ERROR;
ret = regmap_update_bits(smb->regmap, CFG_FAULT_IRQ, 0xff,
enable ? CFG_FAULT_IRQ_DCIN_UV : 0);
if (ret < 0)
goto fail;
ret = smb347_write(smb, CFG_PIN, ret);
}
ret = regmap_update_bits(smb->regmap, CFG_STATUS_IRQ, 0xff,
enable ? CFG_STATUS_IRQ_TERMINATION_OR_TAPER : 0);
if (ret < 0)
goto fail;
ret = regmap_update_bits(smb->regmap, CFG_PIN, CFG_PIN_EN_CHARGER_ERROR,
enable ? CFG_PIN_EN_CHARGER_ERROR : 0);
fail:
smb347_set_writable(smb, false);
return ret;
......@@ -889,18 +797,18 @@ static inline int smb347_irq_disable(struct smb347_charger *smb)
return smb347_irq_set(smb, false);
}
static int smb347_irq_init(struct smb347_charger *smb)
static int smb347_irq_init(struct smb347_charger *smb,
struct i2c_client *client)
{
const struct smb347_charger_platform_data *pdata = smb->pdata;
int ret, irq = gpio_to_irq(pdata->irq_gpio);
ret = gpio_request_one(pdata->irq_gpio, GPIOF_IN, smb->client->name);
ret = gpio_request_one(pdata->irq_gpio, GPIOF_IN, client->name);
if (ret < 0)
goto fail;
ret = request_threaded_irq(irq, NULL, smb347_interrupt,
IRQF_TRIGGER_FALLING, smb->client->name,
smb);
IRQF_TRIGGER_FALLING, client->name, smb);
if (ret < 0)
goto fail_gpio;
......@@ -912,23 +820,14 @@ static int smb347_irq_init(struct smb347_charger *smb)
* Configure the STAT output to be suitable for interrupts: disable
* all other output (except interrupts) and make it active low.
*/
ret = smb347_read(smb, CFG_STAT);
if (ret < 0)
goto fail_readonly;
ret &= ~CFG_STAT_ACTIVE_HIGH;
ret |= CFG_STAT_DISABLED;
ret = smb347_write(smb, CFG_STAT, ret);
if (ret < 0)
goto fail_readonly;
ret = smb347_irq_enable(smb);
ret = regmap_update_bits(smb->regmap, CFG_STAT,
CFG_STAT_ACTIVE_HIGH | CFG_STAT_DISABLED,
CFG_STAT_DISABLED);
if (ret < 0)
goto fail_readonly;
smb347_set_writable(smb, false);
smb->client->irq = irq;
client->irq = irq;
return 0;
fail_readonly:
......@@ -938,7 +837,7 @@ static int smb347_irq_init(struct smb347_charger *smb)
fail_gpio:
gpio_free(pdata->irq_gpio);
fail:
smb->client->irq = 0;
client->irq = 0;
return ret;
}
......@@ -987,13 +886,13 @@ static int smb347_battery_get_property(struct power_supply *psy,
const struct smb347_charger_platform_data *pdata = smb->pdata;
int ret;
ret = smb347_update_status(smb);
ret = smb347_update_ps_status(smb);
if (ret < 0)
return ret;
switch (prop) {
case POWER_SUPPLY_PROP_STATUS:
if (!smb347_is_online(smb)) {
if (!smb347_is_ps_online(smb)) {
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
break;
}
......@@ -1004,7 +903,7 @@ static int smb347_battery_get_property(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
if (!smb347_is_online(smb))
if (!smb347_is_ps_online(smb))
return -ENODATA;
/*
......@@ -1036,44 +935,6 @@ static int smb347_battery_get_property(struct power_supply *psy,
val->intval = pdata->battery_info.voltage_max_design;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
if (!smb347_is_online(smb))
return -ENODATA;
ret = smb347_read(smb, STAT_A);
if (ret < 0)
return ret;
ret &= STAT_A_FLOAT_VOLTAGE_MASK;
if (ret > 0x3d)
ret = 0x3d;
val->intval = 3500000 + ret * 20000;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
if (!smb347_is_online(smb))
return -ENODATA;
ret = smb347_read(smb, STAT_B);
if (ret < 0)
return ret;
/*
* The current value is composition of FCC and PCC values
* and we can detect which table to use from bit 5.
*/
if (ret & 0x20) {
val->intval = hw_to_current(fcc_tbl,
ARRAY_SIZE(fcc_tbl),
ret & 7);
} else {
ret >>= 3;
val->intval = hw_to_current(pcc_tbl,
ARRAY_SIZE(pcc_tbl),
ret & 7);
}
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
val->intval = pdata->battery_info.charge_full_design;
break;
......@@ -1095,64 +956,58 @@ static enum power_supply_property smb347_battery_properties[] = {
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_MODEL_NAME,
};
static int smb347_debugfs_show(struct seq_file *s, void *data)
static bool smb347_volatile_reg(struct device *dev, unsigned int reg)
{
struct smb347_charger *smb = s->private;
int ret;
u8 reg;
seq_printf(s, "Control registers:\n");
seq_printf(s, "==================\n");
for (reg = CFG_CHARGE_CURRENT; reg <= CFG_ADDRESS; reg++) {
ret = smb347_read(smb, reg);
seq_printf(s, "0x%02x:\t0x%02x\n", reg, ret);
}
seq_printf(s, "\n");
seq_printf(s, "Command registers:\n");
seq_printf(s, "==================\n");
ret = smb347_read(smb, CMD_A);
seq_printf(s, "0x%02x:\t0x%02x\n", CMD_A, ret);
ret = smb347_read(smb, CMD_B);
seq_printf(s, "0x%02x:\t0x%02x\n", CMD_B, ret);
ret = smb347_read(smb, CMD_C);
seq_printf(s, "0x%02x:\t0x%02x\n", CMD_C, ret);
seq_printf(s, "\n");
seq_printf(s, "Interrupt status registers:\n");
seq_printf(s, "===========================\n");
for (reg = IRQSTAT_A; reg <= IRQSTAT_F; reg++) {
ret = smb347_read(smb, reg);
seq_printf(s, "0x%02x:\t0x%02x\n", reg, ret);
}
seq_printf(s, "\n");
seq_printf(s, "Status registers:\n");
seq_printf(s, "=================\n");
for (reg = STAT_A; reg <= STAT_E; reg++) {
ret = smb347_read(smb, reg);
seq_printf(s, "0x%02x:\t0x%02x\n", reg, ret);
switch (reg) {
case IRQSTAT_A:
case IRQSTAT_C:
case IRQSTAT_E:
case IRQSTAT_F:
case STAT_A:
case STAT_B:
case STAT_C:
case STAT_E:
return true;
}
return 0;
return false;
}
static int smb347_debugfs_open(struct inode *inode, struct file *file)
static bool smb347_readable_reg(struct device *dev, unsigned int reg)
{
return single_open(file, smb347_debugfs_show, inode->i_private);
switch (reg) {
case CFG_CHARGE_CURRENT:
case CFG_CURRENT_LIMIT:
case CFG_FLOAT_VOLTAGE:
case CFG_STAT:
case CFG_PIN:
case CFG_THERM:
case CFG_SYSOK:
case CFG_OTHER:
case CFG_OTG:
case CFG_TEMP_LIMIT:
case CFG_FAULT_IRQ:
case CFG_STATUS_IRQ:
case CFG_ADDRESS:
case CMD_A:
case CMD_B:
case CMD_C:
return true;
}
return smb347_volatile_reg(dev, reg);
}
static const struct file_operations smb347_debugfs_fops = {
.open = smb347_debugfs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
static const struct regmap_config smb347_regmap = {
.reg_bits = 8,
.val_bits = 8,
.max_register = SMB347_MAX_REGISTER,
.volatile_reg = smb347_volatile_reg,
.readable_reg = smb347_readable_reg,
};
static int smb347_probe(struct i2c_client *client,
......@@ -1178,28 +1033,45 @@ static int smb347_probe(struct i2c_client *client,
i2c_set_clientdata(client, smb);
mutex_init(&smb->lock);
smb->client = client;
smb->dev = &client->dev;
smb->pdata = pdata;
smb->regmap = devm_regmap_init_i2c(client, &smb347_regmap);
if (IS_ERR(smb->regmap))
return PTR_ERR(smb->regmap);
ret = smb347_hw_init(smb);
if (ret < 0)
return ret;
smb->mains.name = "smb347-mains";
smb->mains.type = POWER_SUPPLY_TYPE_MAINS;
smb->mains.get_property = smb347_mains_get_property;
smb->mains.properties = smb347_mains_properties;
smb->mains.num_properties = ARRAY_SIZE(smb347_mains_properties);
smb->mains.supplied_to = battery;
smb->mains.num_supplicants = ARRAY_SIZE(battery);
smb->usb.name = "smb347-usb";
smb->usb.type = POWER_SUPPLY_TYPE_USB;
smb->usb.get_property = smb347_usb_get_property;
smb->usb.properties = smb347_usb_properties;
smb->usb.num_properties = ARRAY_SIZE(smb347_usb_properties);
smb->usb.supplied_to = battery;
smb->usb.num_supplicants = ARRAY_SIZE(battery);
if (smb->pdata->use_mains) {
smb->mains.name = "smb347-mains";
smb->mains.type = POWER_SUPPLY_TYPE_MAINS;
smb->mains.get_property = smb347_mains_get_property;
smb->mains.properties = smb347_mains_properties;
smb->mains.num_properties = ARRAY_SIZE(smb347_mains_properties);
smb->mains.supplied_to = battery;
smb->mains.num_supplicants = ARRAY_SIZE(battery);
ret = power_supply_register(dev, &smb->mains);
if (ret < 0)
return ret;
}
if (smb->pdata->use_usb) {
smb->usb.name = "smb347-usb";
smb->usb.type = POWER_SUPPLY_TYPE_USB;
smb->usb.get_property = smb347_usb_get_property;
smb->usb.properties = smb347_usb_properties;
smb->usb.num_properties = ARRAY_SIZE(smb347_usb_properties);
smb->usb.supplied_to = battery;
smb->usb.num_supplicants = ARRAY_SIZE(battery);
ret = power_supply_register(dev, &smb->usb);
if (ret < 0) {
if (smb->pdata->use_mains)
power_supply_unregister(&smb->mains);
return ret;
}
}
smb->battery.name = "smb347-battery";
smb->battery.type = POWER_SUPPLY_TYPE_BATTERY;
......@@ -1207,20 +1079,13 @@ static int smb347_probe(struct i2c_client *client,
smb->battery.properties = smb347_battery_properties;
smb->battery.num_properties = ARRAY_SIZE(smb347_battery_properties);
ret = power_supply_register(dev, &smb->mains);
if (ret < 0)
return ret;
ret = power_supply_register(dev, &smb->usb);
if (ret < 0) {
power_supply_unregister(&smb->mains);
return ret;
}
ret = power_supply_register(dev, &smb->battery);
if (ret < 0) {
power_supply_unregister(&smb->usb);
power_supply_unregister(&smb->mains);
if (smb->pdata->use_usb)
power_supply_unregister(&smb->usb);
if (smb->pdata->use_mains)
power_supply_unregister(&smb->mains);
return ret;
}
......@@ -1229,15 +1094,15 @@ static int smb347_probe(struct i2c_client *client,
* interrupt support here.
*/
if (pdata->irq_gpio >= 0) {
ret = smb347_irq_init(smb);
ret = smb347_irq_init(smb, client);
if (ret < 0) {
dev_warn(dev, "failed to initialize IRQ: %d\n", ret);
dev_warn(dev, "disabling IRQ support\n");
} else {
smb347_irq_enable(smb);
}
}
smb->dentry = debugfs_create_file("smb347-regs", S_IRUSR, NULL, smb,
&smb347_debugfs_fops);
return 0;
}
......@@ -1245,9 +1110,6 @@ static int smb347_remove(struct i2c_client *client)
{
struct smb347_charger *smb = i2c_get_clientdata(client);
if (!IS_ERR_OR_NULL(smb->dentry))
debugfs_remove(smb->dentry);
if (client->irq) {
smb347_irq_disable(smb);
free_irq(client->irq, smb);
......@@ -1255,8 +1117,10 @@ static int smb347_remove(struct i2c_client *client)
}
power_supply_unregister(&smb->battery);
power_supply_unregister(&smb->usb);
power_supply_unregister(&smb->mains);
if (smb->pdata->use_usb)
power_supply_unregister(&smb->usb);
if (smb->pdata->use_mains)
power_supply_unregister(&smb->mains);
return 0;
}
......@@ -1275,17 +1139,7 @@ static struct i2c_driver smb347_driver = {
.id_table = smb347_id,
};
static int __init smb347_init(void)
{
return i2c_add_driver(&smb347_driver);
}
module_init(smb347_init);
static void __exit smb347_exit(void)
{
i2c_del_driver(&smb347_driver);
}
module_exit(smb347_exit);
module_i2c_driver(smb347_driver);
MODULE_AUTHOR("Bruce E. Robertson <bruce.e.robertson@intel.com>");
MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
......
......@@ -18,6 +18,8 @@
#include <linux/power_supply.h>
enum data_source {
CM_BATTERY_PRESENT,
CM_NO_BATTERY,
CM_FUEL_GAUGE,
CM_CHARGER_STAT,
};
......@@ -29,6 +31,16 @@ enum polling_modes {
CM_POLL_CHARGING_ONLY,
};
enum cm_event_types {
CM_EVENT_UNKNOWN = 0,
CM_EVENT_BATT_FULL,
CM_EVENT_BATT_IN,
CM_EVENT_BATT_OUT,
CM_EVENT_EXT_PWR_IN_OUT,
CM_EVENT_CHG_START_STOP,
CM_EVENT_OTHERS,
};
/**
* struct charger_global_desc
* @rtc_name: the name of RTC used to wake up the system from suspend.
......@@ -38,11 +50,18 @@ enum polling_modes {
* rtc_only_wakeup() returning false.
* If the RTC given to CM is the only wakeup reason,
* rtc_only_wakeup should return true.
* @assume_timer_stops_in_suspend:
* Assume that the jiffy timer stops in suspend-to-RAM.
* When enabled, CM does not rely on jiffies value in
* suspend_again and assumes that jiffies value does not
* change during suspend.
*/
struct charger_global_desc {
char *rtc_name;
bool (*rtc_only_wakeup)(void);
bool assume_timer_stops_in_suspend;
};
/**
......@@ -50,6 +69,11 @@ struct charger_global_desc {
* @psy_name: the name of power-supply-class for charger manager
* @polling_mode:
* Determine which polling mode will be used
* @fullbatt_vchkdrop_ms:
* @fullbatt_vchkdrop_uV:
* Check voltage drop after the battery is fully charged.
* If it has dropped more than fullbatt_vchkdrop_uV after
* fullbatt_vchkdrop_ms, CM will restart charging.
* @fullbatt_uV: voltage in microvolt
* If it is not being charged and VBATT >= fullbatt_uV,
* it is assumed to be full.
......@@ -76,6 +100,8 @@ struct charger_desc {
enum polling_modes polling_mode;
unsigned int polling_interval_ms;
unsigned int fullbatt_vchkdrop_ms;
unsigned int fullbatt_vchkdrop_uV;
unsigned int fullbatt_uV;
enum data_source battery_present;
......@@ -101,6 +127,11 @@ struct charger_desc {
* @fuel_gauge: power_supply for fuel gauge
* @charger_stat: array of power_supply for chargers
* @charger_enabled: the state of charger
* @fullbatt_vchk_jiffies_at:
* jiffies at the time full battery check will occur.
* @fullbatt_vchk_uV: voltage in microvolt
* criteria for full battery
* @fullbatt_vchk_work: work queue for full battery check
* @emergency_stop:
* When setting true, stop charging
* @last_temp_mC: the measured temperature in milli-Celsius
......@@ -121,6 +152,10 @@ struct charger_manager {
bool charger_enabled;
unsigned long fullbatt_vchk_jiffies_at;
unsigned int fullbatt_vchk_uV;
struct delayed_work fullbatt_vchk_work;
int emergency_stop;
int last_temp_mC;
......@@ -134,14 +169,13 @@ struct charger_manager {
#ifdef CONFIG_CHARGER_MANAGER
extern int setup_charger_manager(struct charger_global_desc *gd);
extern bool cm_suspend_again(void);
extern void cm_notify_event(struct power_supply *psy,
enum cm_event_types type, char *msg);
#else
static void __maybe_unused setup_charger_manager(struct charger_global_desc *gd)
{ }
static bool __maybe_unused cm_suspend_again(void)
{
return false;
}
static inline int setup_charger_manager(struct charger_global_desc *gd)
{ return 0; }
static inline bool cm_suspend_again(void) { return false; }
static inline void cm_notify_event(struct power_supply *psy,
enum cm_event_types type, char *msg) { }
#endif
#endif /* _CHARGER_MANAGER_H */
......@@ -116,6 +116,18 @@ enum max17042_register {
MAX17042_VFSOC = 0xFF,
};
/* Registers specific to max17047/50 */
enum max17047_register {
MAX17047_QRTbl00 = 0x12,
MAX17047_FullSOCThr = 0x13,
MAX17047_QRTbl10 = 0x22,
MAX17047_QRTbl20 = 0x32,
MAX17047_V_empty = 0x3A,
MAX17047_QRTbl30 = 0x42,
};
enum max170xx_chip_type {MAX17042, MAX17047};
/*
* used for setting a register to a desired value
* addr : address for a register
......@@ -144,6 +156,7 @@ struct max17042_config_data {
u16 shdntimer; /* 0x03F */
/* App data */
u16 full_soc_thresh; /* 0x13 */
u16 design_cap; /* 0x18 */
u16 ichgt_term; /* 0x1E */
......@@ -162,6 +175,10 @@ struct max17042_config_data {
u16 lavg_empty; /* 0x36 */
u16 dqacc; /* 0x45 */
u16 dpacc; /* 0x46 */
u16 qrtbl00; /* 0x12 */
u16 qrtbl10; /* 0x22 */
u16 qrtbl20; /* 0x32 */
u16 qrtbl30; /* 0x42 */
/* Cell technology from power_supply.h */
u16 cell_technology;
......
......@@ -96,6 +96,7 @@ enum power_supply_property {
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_AVG,
POWER_SUPPLY_PROP_VOLTAGE_OCV,
POWER_SUPPLY_PROP_CURRENT_MAX,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CURRENT_AVG,
......@@ -211,7 +212,7 @@ extern void power_supply_changed(struct power_supply *psy);
extern int power_supply_am_i_supplied(struct power_supply *psy);
extern int power_supply_set_battery_charged(struct power_supply *psy);
#if defined(CONFIG_POWER_SUPPLY) || defined(CONFIG_POWER_SUPPLY_MODULE)
#ifdef CONFIG_POWER_SUPPLY
extern int power_supply_is_system_supplied(void);
#else
static inline int power_supply_is_system_supplied(void) { return -ENOSYS; }
......@@ -261,6 +262,7 @@ static inline bool power_supply_is_watt_property(enum power_supply_property psp)
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
case POWER_SUPPLY_PROP_VOLTAGE_AVG:
case POWER_SUPPLY_PROP_VOLTAGE_OCV:
case POWER_SUPPLY_PROP_POWER_NOW:
return 1;
default:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment