summaryrefslogtreecommitdiffstats
path: root/drivers/power
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2014-10-15 06:56:23 +0200
committerLinus Torvalds <torvalds@linux-foundation.org>2014-10-15 06:56:23 +0200
commit50fa86172bec2769979b5eb0cd1a244391ae4bb0 (patch)
tree5fd4949b031e1362af8b226d6372da2604de13ff /drivers/power
parent6b0490816671b2f4126a99998c9bf3c8c0472de2 (diff)
parent7881c64716f3a7d60b325ed0ad4d15f49b474a43 (diff)
downloadlinux-50fa86172bec2769979b5eb0cd1a244391ae4bb0.tar.bz2
Merge tag 'for-v3.18' of git://git.infradead.org/battery-2.6
Pull power supply and reset updates from Sebastian Reichel: - Initial support for the following chips * max77836 (charger) * max14577 (charger) * bq27742 (battery gauge) * ltc2952 (poweroff) * stih416 (restart) * syscon-reboot (restart) * gpio-restart (restart) - cleanup of power supply core - misc fixes in power supply and reset drivers * tag 'for-v3.18' of git://git.infradead.org/battery-2.6: (48 commits) power: ab8500_fg: Fix build warning Documentation: charger: max14577: Update the date of introducing ABI power: reset: corrections for simple syscon reboot driver Documentation: power: reset: Add documentation for generic SYSCON reboot driver power: reset: Add generic SYSCON register mapped reset bq27x00_battery: Fix flag reading for bq27742 power: reset: use restart_notifier mechanism for msm-poweroff power: Add simple gpio-restart driver power: reset: st: Provide DT bindings for ST's Power Reset driver power: reset: Add restart functionality for STiH41x platforms power: charger-manager: Fix NULL pointer exception with missing cm-fuel-gauge power: max14577: Fix circular config SYSFS dependency power: gpio-charger: do not use gpio value directly power: max8925: Use of_get_child_by_name power: max8925: Fix NULL ptr dereference on memory allocation failure bq27x00_battery: Add support to bq27742 Documentation: charger: max14577: Document exported sysfs entry devicetree: mfd: max14577: Add device tree bindings document power: max17040: Add ID for MAX77836 Fuel Gauge block charger: max14577: Configure battery-dependent settings from DTS and sysfs ... Conflicts: drivers/power/reset/Kconfig drivers/power/reset/Makefile
Diffstat (limited to 'drivers/power')
-rw-r--r--drivers/power/Kconfig5
-rw-r--r--drivers/power/ab8500_fg.c9
-rw-r--r--drivers/power/bq27x00_battery.c57
-rw-r--r--drivers/power/charger-manager.c16
-rw-r--r--drivers/power/gpio-charger.c2
-rw-r--r--drivers/power/max14577_charger.c370
-rw-r--r--drivers/power/max17040_battery.c3
-rw-r--r--drivers/power/max8925_power.c7
-rw-r--r--drivers/power/power_supply_core.c100
-rw-r--r--drivers/power/power_supply_leds.c19
-rw-r--r--drivers/power/power_supply_sysfs.c24
-rw-r--r--drivers/power/reset/Kconfig33
-rw-r--r--drivers/power/reset/Makefile4
-rw-r--r--drivers/power/reset/gpio-restart.c149
-rw-r--r--drivers/power/reset/ltc2952-poweroff.c386
-rw-r--r--drivers/power/reset/msm-poweroff.c20
-rw-r--r--drivers/power/reset/st-poweroff.c151
-rw-r--r--drivers/power/reset/syscon-reboot.c91
-rw-r--r--drivers/power/reset/xgene-reboot.c2
-rw-r--r--drivers/power/sbs-battery.c125
20 files changed, 1429 insertions, 144 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 73cfcdf28a36..0108c2af005b 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -325,11 +325,12 @@ config CHARGER_MANAGER
with help of suspend_again support.
config CHARGER_MAX14577
- tristate "Maxim MAX14577 MUIC battery charger driver"
+ tristate "Maxim MAX14577/77836 battery charger driver"
depends on MFD_MAX14577
+ depends on SYSFS
help
Say Y to enable support for the battery charger control sysfs and
- platform data of MAX14577 MUICs.
+ platform data of MAX14577/77836 MUICs.
config CHARGER_MAX8997
tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver"
diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c
index 3cb4178e397c..217da4b2ca86 100644
--- a/drivers/power/ab8500_fg.c
+++ b/drivers/power/ab8500_fg.c
@@ -2969,7 +2969,7 @@ static struct device_attribute ab8505_fg_sysfs_psy_attrs[] = {
static int ab8500_fg_sysfs_psy_create_attrs(struct device *dev)
{
- unsigned int i, j;
+ unsigned int i;
struct power_supply *psy = dev_get_drvdata(dev);
struct ab8500_fg *di;
@@ -2978,14 +2978,15 @@ static int ab8500_fg_sysfs_psy_create_attrs(struct device *dev)
if (((is_ab8505(di->parent) || is_ab9540(di->parent)) &&
abx500_get_chip_id(dev->parent) >= AB8500_CUT2P0)
|| is_ab8540(di->parent)) {
- for (j = 0; j < ARRAY_SIZE(ab8505_fg_sysfs_psy_attrs); j++)
- if (device_create_file(dev, &ab8505_fg_sysfs_psy_attrs[j]))
+ for (i = 0; i < ARRAY_SIZE(ab8505_fg_sysfs_psy_attrs); i++)
+ if (device_create_file(dev,
+ &ab8505_fg_sysfs_psy_attrs[i]))
goto sysfs_psy_create_attrs_failed_ab8505;
}
return 0;
sysfs_psy_create_attrs_failed_ab8505:
dev_err(dev, "Failed creating sysfs psy attrs for ab8505.\n");
- while (j--)
+ while (i--)
device_remove_file(dev, &ab8505_fg_sysfs_psy_attrs[i]);
return -EIO;
diff --git a/drivers/power/bq27x00_battery.c b/drivers/power/bq27x00_battery.c
index e10763e3a1d5..e3bacfe3bcd0 100644
--- a/drivers/power/bq27x00_battery.c
+++ b/drivers/power/bq27x00_battery.c
@@ -23,6 +23,7 @@
* http://focus.ti.com/docs/prod/folders/print/bq27000.html
* http://focus.ti.com/docs/prod/folders/print/bq27500.html
* http://www.ti.com/product/bq27425-g1
+ * http://www.ti.com/product/BQ27742-G1
*/
#include <linux/device.h>
@@ -71,6 +72,8 @@
#define BQ27500_FLAG_FC BIT(9)
#define BQ27500_FLAG_OTC BIT(15)
+#define BQ27742_POWER_AVG 0x76
+
/* bq27425 register addresses are same as bq27x00 addresses minus 4 */
#define BQ27425_REG_OFFSET 0x04
#define BQ27425_REG_SOC 0x18 /* Register address plus offset */
@@ -83,7 +86,7 @@ struct bq27x00_access_methods {
int (*read)(struct bq27x00_device_info *di, u8 reg, bool single);
};
-enum bq27x00_chip { BQ27000, BQ27500, BQ27425};
+enum bq27x00_chip { BQ27000, BQ27500, BQ27425, BQ27742};
struct bq27x00_reg_cache {
int temperature;
@@ -152,6 +155,24 @@ static enum power_supply_property bq27425_battery_props[] = {
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
};
+static enum power_supply_property bq27742_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_POWER_AVG,
+ POWER_SUPPLY_PROP_HEALTH,
+};
+
static unsigned int poll_interval = 360;
module_param(poll_interval, uint, 0644);
MODULE_PARM_DESC(poll_interval, "battery poll interval in seconds - " \
@@ -176,7 +197,7 @@ static inline int bq27x00_read(struct bq27x00_device_info *di, u8 reg,
*/
static bool bq27xxx_is_chip_version_higher(struct bq27x00_device_info *di)
{
- if (di->chip == BQ27425 || di->chip == BQ27500)
+ if (di->chip == BQ27425 || di->chip == BQ27500 || di->chip == BQ27742)
return true;
return false;
}
@@ -189,7 +210,7 @@ static int bq27x00_battery_read_rsoc(struct bq27x00_device_info *di)
{
int rsoc;
- if (di->chip == BQ27500)
+ if (di->chip == BQ27500 || di->chip == BQ27742)
rsoc = bq27x00_read(di, BQ27500_REG_SOC, false);
else if (di->chip == BQ27425)
rsoc = bq27x00_read(di, BQ27425_REG_SOC, false);
@@ -233,9 +254,11 @@ static inline int bq27x00_battery_read_nac(struct bq27x00_device_info *di)
{
int flags;
bool is_bq27500 = di->chip == BQ27500;
+ bool is_bq27742 = di->chip == BQ27742;
bool is_higher = bq27xxx_is_chip_version_higher(di);
+ bool flags_1b = !(is_bq27500 || is_bq27742);
- flags = bq27x00_read(di, BQ27x00_REG_FLAGS, !is_bq27500);
+ flags = bq27x00_read(di, BQ27x00_REG_FLAGS, flags_1b);
if (flags >= 0 && !is_higher && (flags & BQ27000_FLAG_CI))
return -ENODATA;
@@ -414,13 +437,15 @@ static void bq27x00_update(struct bq27x00_device_info *di)
struct bq27x00_reg_cache cache = {0, };
bool is_bq27500 = di->chip == BQ27500;
bool is_bq27425 = di->chip == BQ27425;
+ bool is_bq27742 = di->chip == BQ27742;
+ bool flags_1b = !(is_bq27500 || is_bq27742);
- cache.flags = bq27x00_read(di, BQ27x00_REG_FLAGS, !is_bq27500);
+ cache.flags = bq27x00_read(di, BQ27x00_REG_FLAGS, flags_1b);
if ((cache.flags & 0xff) == 0xff)
/* read error */
cache.flags = -1;
if (cache.flags >= 0) {
- if (!is_bq27500 && !is_bq27425
+ if (!is_bq27500 && !is_bq27425 && !is_bq27742
&& (cache.flags & BQ27000_FLAG_CI)) {
dev_info(di->dev, "battery is not calibrated! ignoring capacity values\n");
cache.capacity = -ENODATA;
@@ -432,7 +457,11 @@ static void bq27x00_update(struct bq27x00_device_info *di)
cache.health = -ENODATA;
} else {
cache.capacity = bq27x00_battery_read_rsoc(di);
- if (!is_bq27425) {
+ if (is_bq27742)
+ cache.time_to_empty =
+ bq27x00_battery_read_time(di,
+ BQ27x00_REG_TTE);
+ else if (!is_bq27425) {
cache.energy = bq27x00_battery_read_energy(di);
cache.time_to_empty =
bq27x00_battery_read_time(di,
@@ -450,8 +479,14 @@ static void bq27x00_update(struct bq27x00_device_info *di)
cache.temperature = bq27x00_battery_read_temperature(di);
if (!is_bq27425)
cache.cycle_count = bq27x00_battery_read_cyct(di);
- cache.power_avg =
- bq27x00_battery_read_pwr_avg(di, BQ27x00_POWER_AVG);
+ if (is_bq27742)
+ cache.power_avg =
+ bq27x00_battery_read_pwr_avg(di,
+ BQ27742_POWER_AVG);
+ else
+ cache.power_avg =
+ bq27x00_battery_read_pwr_avg(di,
+ BQ27x00_POWER_AVG);
/* We only have to read charge design full once */
if (di->charge_design_full <= 0)
@@ -702,6 +737,9 @@ static int bq27x00_powersupply_init(struct bq27x00_device_info *di)
if (di->chip == BQ27425) {
di->bat.properties = bq27425_battery_props;
di->bat.num_properties = ARRAY_SIZE(bq27425_battery_props);
+ } else if (di->chip == BQ27742) {
+ di->bat.properties = bq27742_battery_props;
+ di->bat.num_properties = ARRAY_SIZE(bq27742_battery_props);
} else {
di->bat.properties = bq27x00_battery_props;
di->bat.num_properties = ARRAY_SIZE(bq27x00_battery_props);
@@ -858,6 +896,7 @@ static const struct i2c_device_id bq27x00_id[] = {
{ "bq27200", BQ27000 }, /* bq27200 is same as bq27000, but with i2c */
{ "bq27500", BQ27500 },
{ "bq27425", BQ27425 },
+ { "bq27742", BQ27742 },
{},
};
MODULE_DEVICE_TABLE(i2c, bq27x00_id);
diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c
index 9e4dab46eefd..7098a1ce2d3c 100644
--- a/drivers/power/charger-manager.c
+++ b/drivers/power/charger-manager.c
@@ -1656,7 +1656,7 @@ static inline struct charger_desc *cm_get_drv_data(struct platform_device *pdev)
{
if (pdev->dev.of_node)
return of_cm_parse_desc(&pdev->dev);
- return (struct charger_desc *)dev_get_platdata(&pdev->dev);
+ return dev_get_platdata(&pdev->dev);
}
static int charger_manager_probe(struct platform_device *pdev)
@@ -1677,7 +1677,7 @@ static int charger_manager_probe(struct platform_device *pdev)
}
}
- if (!desc) {
+ if (IS_ERR(desc)) {
dev_err(&pdev->dev, "No platform data (desc) found\n");
return -ENODEV;
}
@@ -1720,6 +1720,11 @@ static int charger_manager_probe(struct platform_device *pdev)
return -EINVAL;
}
+ if (!desc->psy_fuel_gauge) {
+ dev_err(&pdev->dev, "No fuel gauge power supply defined\n");
+ return -EINVAL;
+ }
+
/* Counting index only */
while (desc->psy_charger_stat[i])
i++;
@@ -1839,6 +1844,13 @@ static int charger_manager_probe(struct platform_device *pdev)
device_init_wakeup(&pdev->dev, true);
device_set_wakeup_capable(&pdev->dev, false);
+ /*
+ * Charger-manager have to check the charging state right after
+ * tialization of charger-manager and then update current charging
+ * state.
+ */
+ cm_monitor();
+
schedule_work(&setup_polling);
return 0;
diff --git a/drivers/power/gpio-charger.c b/drivers/power/gpio-charger.c
index a0024b252197..7536933d0ab9 100644
--- a/drivers/power/gpio-charger.c
+++ b/drivers/power/gpio-charger.c
@@ -55,7 +55,7 @@ static int gpio_charger_get_property(struct power_supply *psy,
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
- val->intval = gpio_get_value_cansleep(pdata->gpio);
+ val->intval = !!gpio_get_value_cansleep(pdata->gpio);
val->intval ^= pdata->gpio_active_low;
break;
default:
diff --git a/drivers/power/max14577_charger.c b/drivers/power/max14577_charger.c
index fad2a75b3604..0a2bc7277026 100644
--- a/drivers/power/max14577_charger.c
+++ b/drivers/power/max14577_charger.c
@@ -1,7 +1,7 @@
/*
- * Battery charger driver for the Maxim 14577
+ * max14577_charger.c - Battery charger driver for the Maxim 14577/77836
*
- * Copyright (C) 2013 Samsung Electronics
+ * Copyright (C) 2013,2014 Samsung Electronics
* Krzysztof Kozlowski <k.kozlowski@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
@@ -19,16 +19,44 @@
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/mfd/max14577-private.h>
+#include <linux/mfd/max14577.h>
struct max14577_charger {
struct device *dev;
struct max14577 *max14577;
struct power_supply charger;
- unsigned int charging_state;
- unsigned int battery_state;
+ unsigned int charging_state;
+ unsigned int battery_state;
+
+ struct max14577_charger_platform_data *pdata;
};
+/*
+ * Helper function for mapping values of STATUS2/CHGTYP register on max14577
+ * and max77836 chipsets to enum maxim_muic_charger_type.
+ */
+static enum max14577_muic_charger_type maxim_get_charger_type(
+ enum maxim_device_type dev_type, u8 val) {
+ switch (val) {
+ case MAX14577_CHARGER_TYPE_NONE:
+ case MAX14577_CHARGER_TYPE_USB:
+ case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT:
+ case MAX14577_CHARGER_TYPE_DEDICATED_CHG:
+ case MAX14577_CHARGER_TYPE_SPECIAL_500MA:
+ case MAX14577_CHARGER_TYPE_SPECIAL_1A:
+ return val;
+ case MAX14577_CHARGER_TYPE_DEAD_BATTERY:
+ case MAX14577_CHARGER_TYPE_RESERVED:
+ if (dev_type == MAXIM_DEVICE_TYPE_MAX77836)
+ val |= 0x8;
+ return val;
+ default:
+ WARN_ONCE(1, "max14577: Unsupported chgtyp register value 0x%02x", val);
+ return val;
+ }
+}
+
static int max14577_get_charger_state(struct max14577_charger *chg)
{
struct regmap *rmap = chg->max14577->regmap;
@@ -89,19 +117,23 @@ static int max14577_get_online(struct max14577_charger *chg)
{
struct regmap *rmap = chg->max14577->regmap;
u8 reg_data;
+ enum max14577_muic_charger_type chg_type;
max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, &reg_data);
reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT);
- switch (reg_data) {
+ chg_type = maxim_get_charger_type(chg->max14577->dev_type, reg_data);
+ switch (chg_type) {
case MAX14577_CHARGER_TYPE_USB:
case MAX14577_CHARGER_TYPE_DEDICATED_CHG:
case MAX14577_CHARGER_TYPE_SPECIAL_500MA:
case MAX14577_CHARGER_TYPE_SPECIAL_1A:
case MAX14577_CHARGER_TYPE_DEAD_BATTERY:
+ case MAX77836_CHARGER_TYPE_SPECIAL_BIAS:
return 1;
case MAX14577_CHARGER_TYPE_NONE:
case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT:
case MAX14577_CHARGER_TYPE_RESERVED:
+ case MAX77836_CHARGER_TYPE_RESERVED:
default:
return 0;
}
@@ -118,10 +150,12 @@ static int max14577_get_battery_health(struct max14577_charger *chg)
struct regmap *rmap = chg->max14577->regmap;
int state = POWER_SUPPLY_HEALTH_GOOD;
u8 reg_data;
+ enum max14577_muic_charger_type chg_type;
max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, &reg_data);
reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT);
- if (reg_data == MAX14577_CHARGER_TYPE_DEAD_BATTERY) {
+ chg_type = maxim_get_charger_type(chg->max14577->dev_type, reg_data);
+ if (chg_type == MAX14577_CHARGER_TYPE_DEAD_BATTERY) {
state = POWER_SUPPLY_HEALTH_DEAD;
goto state_set;
}
@@ -147,15 +181,131 @@ static int max14577_get_present(struct max14577_charger *chg)
return 1;
}
+static int max14577_set_fast_charge_timer(struct max14577_charger *chg,
+ unsigned long hours)
+{
+ u8 reg_data;
+
+ switch (hours) {
+ case 5 ... 7:
+ reg_data = hours - 3;
+ break;
+ case 0:
+ /* Disable */
+ reg_data = 0x7;
+ break;
+ default:
+ dev_err(chg->dev, "Wrong value for Fast-Charge Timer: %lu\n",
+ hours);
+ return -EINVAL;
+ }
+ reg_data <<= CHGCTRL1_TCHW_SHIFT;
+
+ return max14577_update_reg(chg->max14577->regmap,
+ MAX14577_REG_CHGCTRL1, CHGCTRL1_TCHW_MASK, reg_data);
+}
+
+static int max14577_init_constant_voltage(struct max14577_charger *chg,
+ unsigned int uvolt)
+{
+ u8 reg_data;
+
+ if (uvolt < MAXIM_CHARGER_CONSTANT_VOLTAGE_MIN ||
+ uvolt > MAXIM_CHARGER_CONSTANT_VOLTAGE_MAX)
+ return -EINVAL;
+
+ if (uvolt == 4200000)
+ reg_data = 0x0;
+ else if (uvolt == MAXIM_CHARGER_CONSTANT_VOLTAGE_MAX)
+ reg_data = 0x1f;
+ else if (uvolt <= 4280000) {
+ unsigned int val = uvolt;
+
+ val -= MAXIM_CHARGER_CONSTANT_VOLTAGE_MIN;
+ val /= MAXIM_CHARGER_CONSTANT_VOLTAGE_STEP;
+ if (uvolt <= 4180000)
+ reg_data = 0x1 + val;
+ else
+ reg_data = val; /* Fix for gap between 4.18V and 4.22V */
+ } else
+ return -EINVAL;
+
+ reg_data <<= CHGCTRL3_MBCCVWRC_SHIFT;
+
+ return max14577_write_reg(chg->max14577->regmap,
+ MAX14577_CHG_REG_CHG_CTRL3, reg_data);
+}
+
+static int max14577_init_eoc(struct max14577_charger *chg,
+ unsigned int uamp)
+{
+ unsigned int current_bits = 0xf;
+ u8 reg_data;
+
+ switch (chg->max14577->dev_type) {
+ case MAXIM_DEVICE_TYPE_MAX77836:
+ if (uamp < 5000)
+ return -EINVAL; /* Requested current is too low */
+
+ if (uamp >= 7500 && uamp < 10000)
+ current_bits = 0x0;
+ else if (uamp <= 50000) {
+ /* <5000, 7499> and <10000, 50000> */
+ current_bits = uamp / 5000;
+ } else {
+ uamp = min(uamp, 100000U) - 50000U;
+ current_bits = 0xa + uamp / 10000;
+ }
+ break;
+
+ case MAXIM_DEVICE_TYPE_MAX14577:
+ default:
+ if (uamp < MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN)
+ return -EINVAL; /* Requested current is too low */
+
+ uamp = min(uamp, MAX14577_CHARGER_EOC_CURRENT_LIMIT_MAX);
+ uamp -= MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN;
+ current_bits = uamp / MAX14577_CHARGER_EOC_CURRENT_LIMIT_STEP;
+ break;
+ }
+
+ reg_data = current_bits << CHGCTRL5_EOCS_SHIFT;
+
+ return max14577_update_reg(chg->max14577->regmap,
+ MAX14577_CHG_REG_CHG_CTRL5, CHGCTRL5_EOCS_MASK,
+ reg_data);
+}
+
+static int max14577_init_fast_charge(struct max14577_charger *chg,
+ unsigned int uamp)
+{
+ u8 reg_data;
+ int ret;
+ const struct maxim_charger_current *limits =
+ &maxim_charger_currents[chg->max14577->dev_type];
+
+ ret = maxim_charger_calc_reg_current(limits, uamp, uamp, &reg_data);
+ if (ret) {
+ dev_err(chg->dev, "Wrong value for fast charge: %u\n", uamp);
+ return ret;
+ }
+
+ return max14577_update_reg(chg->max14577->regmap,
+ MAX14577_CHG_REG_CHG_CTRL4,
+ CHGCTRL4_MBCICHWRCL_MASK | CHGCTRL4_MBCICHWRCH_MASK,
+ reg_data);
+}
+
/*
* Sets charger registers to proper and safe default values.
* Some of these values are equal to defaults in MAX14577E
* data sheet but there are minor differences.
*/
-static void max14577_charger_reg_init(struct max14577_charger *chg)
+static int max14577_charger_reg_init(struct max14577_charger *chg)
{
struct regmap *rmap = chg->max14577->regmap;
u8 reg_data;
+ int ret;
/*
* Charger-Type Manual Detection, default off (set CHGTYPMAN to 0)
@@ -167,10 +317,6 @@ static void max14577_charger_reg_init(struct max14577_charger *chg)
CDETCTRL1_CHGDETEN_MASK | CDETCTRL1_CHGTYPMAN_MASK,
reg_data);
- /* Battery Fast-Charge Timer, from SM-V700: 6hrs */
- reg_data = 0x3 << CHGCTRL1_TCHW_SHIFT;
- max14577_write_reg(rmap, MAX14577_REG_CHGCTRL1, reg_data);
-
/*
* Wall-Adapter Rapid Charge, default on
* Battery-Charger, default on
@@ -179,29 +325,46 @@ static void max14577_charger_reg_init(struct max14577_charger *chg)
reg_data |= 0x1 << CHGCTRL2_MBCHOSTEN_SHIFT;
max14577_write_reg(rmap, MAX14577_REG_CHGCTRL2, reg_data);
- /* Battery-Charger Constant Voltage (CV) Mode, from SM-V700: 4.35V */
- reg_data = 0xf << CHGCTRL3_MBCCVWRC_SHIFT;
- max14577_write_reg(rmap, MAX14577_REG_CHGCTRL3, reg_data);
-
- /*
- * Fast Battery-Charge Current Low, default 200-950mA
- * Fast Battery-Charge Current High, from SM-V700: 450mA
- */
- reg_data = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT;
- reg_data |= 0x5 << CHGCTRL4_MBCICHWRCH_SHIFT;
- max14577_write_reg(rmap, MAX14577_REG_CHGCTRL4, reg_data);
-
- /* End-of-Charge Current, from SM-V700: 50mA */
- reg_data = 0x0 << CHGCTRL5_EOCS_SHIFT;
- max14577_write_reg(rmap, MAX14577_REG_CHGCTRL5, reg_data);
-
/* Auto Charging Stop, default off */
reg_data = 0x0 << CHGCTRL6_AUTOSTOP_SHIFT;
max14577_write_reg(rmap, MAX14577_REG_CHGCTRL6, reg_data);
- /* Overvoltage-Protection Threshold, from SM-V700: 6.5V */
- reg_data = 0x2 << CHGCTRL7_OTPCGHCVS_SHIFT;
+ ret = max14577_init_constant_voltage(chg, chg->pdata->constant_uvolt);
+ if (ret)
+ return ret;
+
+ ret = max14577_init_eoc(chg, chg->pdata->eoc_uamp);
+ if (ret)
+ return ret;
+
+ ret = max14577_init_fast_charge(chg, chg->pdata->fast_charge_uamp);
+ if (ret)
+ return ret;
+
+ ret = max14577_set_fast_charge_timer(chg,
+ MAXIM_CHARGER_FAST_CHARGE_TIMER_DEFAULT);
+ if (ret)
+ return ret;
+
+ /* Initialize Overvoltage-Protection Threshold */
+ switch (chg->pdata->ovp_uvolt) {
+ case 7500000:
+ reg_data = 0x0;
+ break;
+ case 6000000:
+ case 6500000:
+ case 7000000:
+ reg_data = 0x1 + (chg->pdata->ovp_uvolt - 6000000) / 500000;
+ break;
+ default:
+ dev_err(chg->dev, "Wrong value for OVP: %u\n",
+ chg->pdata->ovp_uvolt);
+ return -EINVAL;
+ }
+ reg_data <<= CHGCTRL7_OTPCGHCVS_SHIFT;
max14577_write_reg(rmap, MAX14577_REG_CHGCTRL7, reg_data);
+
+ return 0;
}
/* Support property from charger */
@@ -215,7 +378,11 @@ static enum power_supply_property max14577_charger_props[] = {
POWER_SUPPLY_PROP_MANUFACTURER,
};
-static const char *model_name = "MAX14577";
+static const char * const model_names[] = {
+ [MAXIM_DEVICE_TYPE_UNKNOWN] = "MAX14577-like",
+ [MAXIM_DEVICE_TYPE_MAX14577] = "MAX14577",
+ [MAXIM_DEVICE_TYPE_MAX77836] = "MAX77836",
+};
static const char *manufacturer = "Maxim Integrated";
static int max14577_charger_get_property(struct power_supply *psy,
@@ -244,7 +411,8 @@ static int max14577_charger_get_property(struct power_supply *psy,
val->intval = max14577_get_online(chg);
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
- val->strval = model_name;
+ BUILD_BUG_ON(ARRAY_SIZE(model_names) != MAXIM_DEVICE_TYPE_NUM);
+ val->strval = model_names[chg->max14577->dev_type];
break;
case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = manufacturer;
@@ -256,6 +424,110 @@ static int max14577_charger_get_property(struct power_supply *psy,
return ret;
}
+#ifdef CONFIG_OF
+static struct max14577_charger_platform_data *max14577_charger_dt_init(
+ struct platform_device *pdev)
+{
+ struct max14577_charger_platform_data *pdata;
+ struct device_node *np = pdev->dev.of_node;
+ int ret;
+
+ if (!np) {
+ dev_err(&pdev->dev, "No charger OF node\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ ret = of_property_read_u32(np, "maxim,constant-uvolt",
+ &pdata->constant_uvolt);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot parse maxim,constant-uvolt field from DT\n");
+ return ERR_PTR(ret);
+ }
+
+ ret = of_property_read_u32(np, "maxim,fast-charge-uamp",
+ &pdata->fast_charge_uamp);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot parse maxim,fast-charge-uamp field from DT\n");
+ return ERR_PTR(ret);
+ }
+
+ ret = of_property_read_u32(np, "maxim,eoc-uamp", &pdata->eoc_uamp);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot parse maxim,eoc-uamp field from DT\n");
+ return ERR_PTR(ret);
+ }
+
+ ret = of_property_read_u32(np, "maxim,ovp-uvolt", &pdata->ovp_uvolt);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot parse maxim,ovp-uvolt field from DT\n");
+ return ERR_PTR(ret);
+ }
+
+ return pdata;
+}
+#else /* CONFIG_OF */
+static struct max14577_charger_platform_data *max14577_charger_dt_init(
+ struct platform_device *pdev)
+{
+ return NULL;
+}
+#endif /* CONFIG_OF */
+
+static ssize_t show_fast_charge_timer(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct max14577_charger *chg = dev_get_drvdata(dev);
+ u8 reg_data;
+ int ret;
+ unsigned int val;
+
+ ret = max14577_read_reg(chg->max14577->regmap, MAX14577_REG_CHGCTRL1,
+ &reg_data);
+ if (ret)
+ return ret;
+
+ reg_data &= CHGCTRL1_TCHW_MASK;
+ reg_data >>= CHGCTRL1_TCHW_SHIFT;
+ switch (reg_data) {
+ case 0x2 ... 0x4:
+ val = reg_data + 3;
+ break;
+ case 0x7:
+ val = 0;
+ break;
+ default:
+ val = 5;
+ break;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_fast_charge_timer(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct max14577_charger *chg = dev_get_drvdata(dev);
+ unsigned long val;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ ret = max14577_set_fast_charge_timer(chg, val);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static DEVICE_ATTR(fast_charge_timer, S_IRUGO | S_IWUSR,
+ show_fast_charge_timer, store_fast_charge_timer);
+
static int max14577_charger_probe(struct platform_device *pdev)
{
struct max14577_charger *chg;
@@ -270,7 +542,13 @@ static int max14577_charger_probe(struct platform_device *pdev)
chg->dev = &pdev->dev;
chg->max14577 = max14577;
- max14577_charger_reg_init(chg);
+ chg->pdata = max14577_charger_dt_init(pdev);
+ if (IS_ERR_OR_NULL(chg->pdata))
+ return PTR_ERR(chg->pdata);
+
+ ret = max14577_charger_reg_init(chg);
+ if (ret)
+ return ret;
chg->charger.name = "max14577-charger",
chg->charger.type = POWER_SUPPLY_TYPE_BATTERY,
@@ -278,24 +556,47 @@ static int max14577_charger_probe(struct platform_device *pdev)
chg->charger.num_properties = ARRAY_SIZE(max14577_charger_props),
chg->charger.get_property = max14577_charger_get_property,
+ ret = device_create_file(&pdev->dev, &dev_attr_fast_charge_timer);
+ if (ret) {
+ dev_err(&pdev->dev, "failed: create sysfs entry\n");
+ return ret;
+ }
+
ret = power_supply_register(&pdev->dev, &chg->charger);
if (ret) {
dev_err(&pdev->dev, "failed: power supply register\n");
- return ret;
+ goto err;
}
+ /* Check for valid values for charger */
+ BUILD_BUG_ON(MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN +
+ MAX14577_CHARGER_EOC_CURRENT_LIMIT_STEP * 0xf !=
+ MAX14577_CHARGER_EOC_CURRENT_LIMIT_MAX);
return 0;
+
+err:
+ device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer);
+
+ return ret;
}
static int max14577_charger_remove(struct platform_device *pdev)
{
struct max14577_charger *chg = platform_get_drvdata(pdev);
+ device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer);
power_supply_unregister(&chg->charger);
return 0;
}
+static const struct platform_device_id max14577_charger_id[] = {
+ { "max14577-charger", MAXIM_DEVICE_TYPE_MAX14577, },
+ { "max77836-charger", MAXIM_DEVICE_TYPE_MAX77836, },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, max14577_charger_id);
+
static struct platform_driver max14577_charger_driver = {
.driver = {
.owner = THIS_MODULE,
@@ -303,9 +604,10 @@ static struct platform_driver max14577_charger_driver = {
},
.probe = max14577_charger_probe,
.remove = max14577_charger_remove,
+ .id_table = max14577_charger_id,
};
module_platform_driver(max14577_charger_driver);
MODULE_AUTHOR("Krzysztof Kozlowski <k.kozlowski@samsung.com>");
-MODULE_DESCRIPTION("MAXIM 14577 charger driver");
+MODULE_DESCRIPTION("Maxim 14577/77836 charger driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/power/max17040_battery.c b/drivers/power/max17040_battery.c
index 0fbac861080d..14d44706327b 100644
--- a/drivers/power/max17040_battery.c
+++ b/drivers/power/max17040_battery.c
@@ -277,7 +277,8 @@ static SIMPLE_DEV_PM_OPS(max17040_pm_ops, max17040_suspend, max17040_resume);
#endif /* CONFIG_PM_SLEEP */
static const struct i2c_device_id max17040_id[] = {
- { "max17040", 0 },
+ { "max17040" },
+ { "max77836-battery" },
{ }
};
MODULE_DEVICE_TABLE(i2c, max17040_id);
diff --git a/drivers/power/max8925_power.c b/drivers/power/max8925_power.c
index b4513f284bbc..a6d45eef64dd 100644
--- a/drivers/power/max8925_power.c
+++ b/drivers/power/max8925_power.c
@@ -443,7 +443,7 @@ max8925_power_dt_init(struct platform_device *pdev)
if (!nproot)
return pdev->dev.platform_data;
- np = of_find_node_by_name(nproot, "charger");
+ np = of_get_child_by_name(nproot, "charger");
if (!np) {
dev_err(&pdev->dev, "failed to find charger node\n");
return NULL;
@@ -452,13 +452,14 @@ max8925_power_dt_init(struct platform_device *pdev)
pdata = devm_kzalloc(&pdev->dev,
sizeof(struct max8925_power_pdata),
GFP_KERNEL);
+ if (!pdata)
+ goto ret;
of_property_read_u32(np, "topoff-threshold", &topoff_threshold);
of_property_read_u32(np, "batt-detect", &batt_detect);
of_property_read_u32(np, "fast-charge", &fast_charge);
of_property_read_u32(np, "no-insert-detect", &no_insert_detect);
of_property_read_u32(np, "no-temp-support", &no_temp_support);
- of_node_put(np);
pdata->batt_detect = batt_detect;
pdata->fast_charge = fast_charge;
@@ -466,6 +467,8 @@ max8925_power_dt_init(struct platform_device *pdev)
pdata->no_insert_detect = no_insert_detect;
pdata->no_temp_support = no_temp_support;
+ret:
+ of_node_put(np);
return pdata;
}
#else
diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c
index 078afd61490d..6cb7fe5c022d 100644
--- a/drivers/power/power_supply_core.c
+++ b/drivers/power/power_supply_core.c
@@ -58,7 +58,7 @@ static bool __power_supply_is_supplied_by(struct power_supply *supplier,
static int __power_supply_changed_work(struct device *dev, void *data)
{
- struct power_supply *psy = (struct power_supply *)data;
+ struct power_supply *psy = data;
struct power_supply *pst = dev_get_drvdata(dev);
if (__power_supply_is_supplied_by(psy, pst)) {
@@ -78,7 +78,14 @@ static void power_supply_changed_work(struct work_struct *work)
dev_dbg(psy->dev, "%s\n", __func__);
spin_lock_irqsave(&psy->changed_lock, flags);
- if (psy->changed) {
+ /*
+ * Check 'changed' here to avoid issues due to race between
+ * power_supply_changed() and this routine. In worst case
+ * power_supply_changed() can be called again just before we take above
+ * lock. During the first call of this routine we will mark 'changed' as
+ * false and it will stay false for the next call as well.
+ */
+ if (likely(psy->changed)) {
psy->changed = false;
spin_unlock_irqrestore(&psy->changed_lock, flags);
class_for_each_device(power_supply_class, NULL, psy,
@@ -89,12 +96,13 @@ static void power_supply_changed_work(struct work_struct *work)
kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE);
spin_lock_irqsave(&psy->changed_lock, flags);
}
+
/*
- * Dependent power supplies (e.g. battery) may have changed state
- * as a result of this event, so poll again and hold the
- * wakeup_source until all events are processed.
+ * Hold the wakeup_source until all events are processed.
+ * power_supply_changed() might have called again and have set 'changed'
+ * to true.
*/
- if (!psy->changed)
+ if (likely(!psy->changed))
pm_relax(psy->dev);
spin_unlock_irqrestore(&psy->changed_lock, flags);
}
@@ -119,7 +127,7 @@ EXPORT_SYMBOL_GPL(power_supply_changed);
static int __power_supply_populate_supplied_from(struct device *dev,
void *data)
{
- struct power_supply *psy = (struct power_supply *)data;
+ struct power_supply *psy = data;
struct power_supply *epsy = dev_get_drvdata(dev);
struct device_node *np;
int i = 0;
@@ -127,7 +135,7 @@ static int __power_supply_populate_supplied_from(struct device *dev,
do {
np = of_parse_phandle(psy->of_node, "power-supplies", i++);
if (!np)
- continue;
+ break;
if (np == epsy->of_node) {
dev_info(psy->dev, "%s: Found supply : %s\n",
@@ -158,12 +166,12 @@ static int power_supply_populate_supplied_from(struct power_supply *psy)
static int __power_supply_find_supply_from_node(struct device *dev,
void *data)
{
- struct device_node *np = (struct device_node *)data;
+ struct device_node *np = data;
struct power_supply *epsy = dev_get_drvdata(dev);
- /* return error breaks out of class_for_each_device loop */
+ /* returning non-zero breaks out of class_for_each_device loop */
if (epsy->of_node == np)
- return -EINVAL;
+ return 1;
return 0;
}
@@ -171,30 +179,21 @@ static int __power_supply_find_supply_from_node(struct device *dev,
static int power_supply_find_supply_from_node(struct device_node *supply_node)
{
int error;
- struct device *dev;
- struct class_dev_iter iter;
/*
- * Use iterator to see if any other device is registered.
- * This is required since class_for_each_device returns 0
- * if there are no devices registered.
- */
- class_dev_iter_init(&iter, power_supply_class, NULL, NULL);
- dev = class_dev_iter_next(&iter);
-
- if (!dev)
- return -EPROBE_DEFER;
-
- /*
- * We have to treat the return value as inverted, because if
- * we return error on not found, then it won't continue looking.
- * So we trick it by returning error on success to stop looking
- * once the matching device is found.
+ * class_for_each_device() either returns its own errors or values
+ * returned by __power_supply_find_supply_from_node().
+ *
+ * __power_supply_find_supply_from_node() will return 0 (no match)
+ * or 1 (match).
+ *
+ * We return 0 if class_for_each_device() returned 1, -EPROBE_DEFER if
+ * it returned 0, or error as returned by it.
*/
error = class_for_each_device(power_supply_class, NULL, supply_node,
__power_supply_find_supply_from_node);
- return error ? 0 : -EPROBE_DEFER;
+ return error ? (error == 1 ? 0 : error) : -EPROBE_DEFER;
}
static int power_supply_check_supplies(struct power_supply *psy)
@@ -215,17 +214,21 @@ static int power_supply_check_supplies(struct power_supply *psy)
np = of_parse_phandle(psy->of_node, "power-supplies", cnt++);
if (!np)
- continue;
+ break;
ret = power_supply_find_supply_from_node(np);
+ of_node_put(np);
+
if (ret) {
- dev_dbg(psy->dev, "Failed to find supply, defer!\n");
- of_node_put(np);
- return -EPROBE_DEFER;
+ dev_dbg(psy->dev, "Failed to find supply!\n");
+ return ret;
}
- of_node_put(np);
} while (np);
+ /* Missing valid "power-supplies" entries */
+ if (cnt == 1)
+ return 0;
+
/* All supplies found, allocate char ** array for filling */
psy->supplied_from = devm_kzalloc(psy->dev, sizeof(psy->supplied_from),
GFP_KERNEL);
@@ -234,7 +237,7 @@ static int power_supply_check_supplies(struct power_supply *psy)
return -ENOMEM;
}
- *psy->supplied_from = devm_kzalloc(psy->dev, sizeof(char *) * cnt,
+ *psy->supplied_from = devm_kzalloc(psy->dev, sizeof(char *) * (cnt - 1),
GFP_KERNEL);
if (!*psy->supplied_from) {
dev_err(psy->dev, "Couldn't allocate memory for supply list\n");
@@ -253,14 +256,12 @@ static inline int power_supply_check_supplies(struct power_supply *psy)
static int __power_supply_am_i_supplied(struct device *dev, void *data)
{
union power_supply_propval ret = {0,};
- struct power_supply *psy = (struct power_supply *)data;
+ struct power_supply *psy = data;
struct power_supply *epsy = dev_get_drvdata(dev);
if (__power_supply_is_supplied_by(epsy, psy))
- if (!epsy->get_property(epsy, POWER_SUPPLY_PROP_ONLINE, &ret)) {
- if (ret.intval)
- return ret.intval;
- }
+ if (!epsy->get_property(epsy, POWER_SUPPLY_PROP_ONLINE, &ret))
+ return ret.intval;
return 0;
}
@@ -285,12 +286,10 @@ static int __power_supply_is_system_supplied(struct device *dev, void *data)
unsigned int *count = data;
(*count)++;
- if (psy->type != POWER_SUPPLY_TYPE_BATTERY) {
- if (psy->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &ret))
- return 0;
- if (ret.intval)
+ if (psy->type != POWER_SUPPLY_TYPE_BATTERY)
+ if (!psy->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &ret))
return ret.intval;
- }
+
return 0;
}
@@ -423,9 +422,7 @@ static int psy_register_thermal(struct power_supply *psy)
if (psy->properties[i] == POWER_SUPPLY_PROP_TEMP) {
psy->tzd = thermal_zone_device_register(psy->name, 0, 0,
psy, &psy_tzd_ops, NULL, 0, 0);
- if (IS_ERR(psy->tzd))
- return PTR_ERR(psy->tzd);
- break;
+ return PTR_ERR_OR_ZERO(psy->tzd);
}
}
return 0;
@@ -503,9 +500,7 @@ static int psy_register_cooler(struct power_supply *psy)
psy->tcd = thermal_cooling_device_register(
(char *)psy->name,
psy, &psy_tcd_ops);
- if (IS_ERR(psy->tcd))
- return PTR_ERR(psy->tcd);
- break;
+ return PTR_ERR_OR_ZERO(psy->tcd);
}
}
return 0;
@@ -591,7 +586,7 @@ static int __power_supply_register(struct device *parent,
power_supply_changed(psy);
- goto success;
+ return 0;
create_triggers_failed:
psy_unregister_cooler(psy);
@@ -604,7 +599,6 @@ wakeup_init_failed:
check_supplies_failed:
dev_set_name_failed:
put_device(dev);
-success:
return rc;
}
diff --git a/drivers/power/power_supply_leds.c b/drivers/power/power_supply_leds.c
index 995f966ed5b7..effa093c37b0 100644
--- a/drivers/power/power_supply_leds.c
+++ b/drivers/power/power_supply_leds.c
@@ -57,8 +57,6 @@ static void power_supply_update_bat_leds(struct power_supply *psy)
static int power_supply_create_bat_triggers(struct power_supply *psy)
{
- int rc = 0;
-
psy->charging_full_trig_name = kasprintf(GFP_KERNEL,
"%s-charging-or-full", psy->name);
if (!psy->charging_full_trig_name)
@@ -87,7 +85,7 @@ static int power_supply_create_bat_triggers(struct power_supply *psy)
led_trigger_register_simple(psy->charging_blink_full_solid_trig_name,
&psy->charging_blink_full_solid_trig);
- goto success;
+ return 0;
charging_blink_full_solid_failed:
kfree(psy->full_trig_name);
@@ -96,9 +94,7 @@ full_failed:
charging_failed:
kfree(psy->charging_full_trig_name);
charging_full_failed:
- rc = -ENOMEM;
-success:
- return rc;
+ return -ENOMEM;
}
static void power_supply_remove_bat_triggers(struct power_supply *psy)
@@ -132,20 +128,13 @@ static void power_supply_update_gen_leds(struct power_supply *psy)
static int power_supply_create_gen_triggers(struct power_supply *psy)
{
- int rc = 0;
-
psy->online_trig_name = kasprintf(GFP_KERNEL, "%s-online", psy->name);
if (!psy->online_trig_name)
- goto online_failed;
+ return -ENOMEM;
led_trigger_register_simple(psy->online_trig_name, &psy->online_trig);
- goto success;
-
-online_failed:
- rc = -ENOMEM;
-success:
- return rc;
+ return 0;
}
static void power_supply_remove_gen_triggers(struct power_supply *psy)
diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c
index 750a20275664..62653f50a524 100644
--- a/drivers/power/power_supply_sysfs.c
+++ b/drivers/power/power_supply_sysfs.c
@@ -73,19 +73,20 @@ static ssize_t power_supply_show_property(struct device *dev,
const ptrdiff_t off = attr - power_supply_attrs;
union power_supply_propval value;
- if (off == POWER_SUPPLY_PROP_TYPE)
+ if (off == POWER_SUPPLY_PROP_TYPE) {
value.intval = psy->type;
- else
+ } else {
ret = psy->get_property(psy, off, &value);
- if (ret < 0) {
- if (ret == -ENODATA)
- dev_dbg(dev, "driver has no data for `%s' property\n",
- attr->attr.name);
- else if (ret != -ENODEV)
- dev_err(dev, "driver failed to report `%s' property: %zd\n",
- attr->attr.name, ret);
- return ret;
+ if (ret < 0) {
+ if (ret == -ENODATA)
+ dev_dbg(dev, "driver has no data for `%s' property\n",
+ attr->attr.name);
+ else if (ret != -ENODEV)
+ dev_err(dev, "driver failed to report `%s' property: %zd\n",
+ attr->attr.name, ret);
+ return ret;
+ }
}
if (off == POWER_SUPPLY_PROP_STATUS)
@@ -149,9 +150,11 @@ static struct device_attribute power_supply_attrs[] = {
POWER_SUPPLY_ATTR(voltage_now),
POWER_SUPPLY_ATTR(voltage_avg),
POWER_SUPPLY_ATTR(voltage_ocv),
+ POWER_SUPPLY_ATTR(voltage_boot),
POWER_SUPPLY_ATTR(current_max),
POWER_SUPPLY_ATTR(current_now),
POWER_SUPPLY_ATTR(current_avg),
+ POWER_SUPPLY_ATTR(current_boot),
POWER_SUPPLY_ATTR(power_now),
POWER_SUPPLY_ATTR(power_avg),
POWER_SUPPLY_ATTR(charge_full_design),
@@ -193,6 +196,7 @@ static struct device_attribute power_supply_attrs[] = {
POWER_SUPPLY_ATTR(type),
POWER_SUPPLY_ATTR(scope),
POWER_SUPPLY_ATTR(charge_term_current),
+ POWER_SUPPLY_ATTR(calibrate),
/* Properties of type `const char *' */
POWER_SUPPLY_ATTR(model_name),
POWER_SUPPLY_ATTR(manufacturer),
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index 527a0f47ef44..f65ff49bb275 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -40,7 +40,7 @@ config POWER_RESET_AXXIA
config POWER_RESET_BRCMSTB
bool "Broadcom STB reset driver" if COMPILE_TEST
- depends on POWER_RESET && ARM
+ depends on ARM
default ARCH_BRCMSTB
help
This driver provides restart support for ARM-based Broadcom STB
@@ -57,9 +57,17 @@ config POWER_RESET_GPIO
If your board needs a GPIO high/low to power down, say Y and
create a binding in your devicetree.
+config POWER_RESET_GPIO_RESTART
+ bool "GPIO restart driver"
+ depends on OF_GPIO
+ help
+ This driver supports restarting your board via a GPIO line.
+ If your board needs a GPIO high/low to restart, say Y and
+ create a binding in your devicetree.
+
config POWER_RESET_HISI
bool "Hisilicon power-off driver"
- depends on POWER_RESET && ARCH_HISI
+ depends on ARCH_HISI
help
Reboot support for Hisilicon boards.
@@ -69,6 +77,13 @@ config POWER_RESET_MSM
help
Power off and restart support for Qualcomm boards.
+config POWER_RESET_LTC2952
+ bool "LTC2952 PowerPath power-off driver"
+ depends on OF_GPIO
+ help
+ This driver supports an external powerdown trigger and board power
+ down via the LTC2952. Bindings are made in the device tree.
+
config POWER_RESET_QNAP
bool "QNAP power-off driver"
depends on OF_GPIO && PLAT_ORION
@@ -92,6 +107,12 @@ config POWER_RESET_SUN6I
help
Reboot support for the Allwinner A31 SoCs.
+config POWER_RESET_ST
+ bool "ST restart power-off driver"
+ depends on ARCH_STI
+ help
+ Power off and reset support for STMicroelectronics boards.
+
config POWER_RESET_VERSATILE
bool "ARM Versatile family reboot driver"
depends on ARM
@@ -122,4 +143,12 @@ config POWER_RESET_KEYSTONE
help
Reboot support for the KEYSTONE SoCs.
+config POWER_RESET_SYSCON
+ bool "Generic SYSCON regmap reset driver"
+ depends on OF
+ select MFD_SYSCON
+ help
+ Reboot support for generic SYSCON mapped register reset.
+
endif
+
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
index 73221009f2bf..76ce1c59469b 100644
--- a/drivers/power/reset/Makefile
+++ b/drivers/power/reset/Makefile
@@ -4,12 +4,16 @@ obj-$(CONFIG_POWER_RESET_AT91_RESET) += at91-reset.o
obj-$(CONFIG_POWER_RESET_AXXIA) += axxia-reset.o
obj-$(CONFIG_POWER_RESET_BRCMSTB) += brcmstb-reboot.o
obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o
+obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o
obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o
obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o
+obj-$(CONFIG_POWER_RESET_LTC2952) += ltc2952-poweroff.o
obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o
obj-$(CONFIG_POWER_RESET_RESTART) += restart-poweroff.o
obj-$(CONFIG_POWER_RESET_SUN6I) += sun6i-reboot.o
+obj-$(CONFIG_POWER_RESET_ST) += st-poweroff.o
obj-$(CONFIG_POWER_RESET_VERSATILE) += arm-versatile-reboot.o
obj-$(CONFIG_POWER_RESET_VEXPRESS) += vexpress-poweroff.o
obj-$(CONFIG_POWER_RESET_XGENE) += xgene-reboot.o
obj-$(CONFIG_POWER_RESET_KEYSTONE) += keystone-reset.o
+obj-$(CONFIG_POWER_RESET_SYSCON) += syscon-reboot.o
diff --git a/drivers/power/reset/gpio-restart.c b/drivers/power/reset/gpio-restart.c
new file mode 100644
index 000000000000..a76829b3f1cd
--- /dev/null
+++ b/drivers/power/reset/gpio-restart.c
@@ -0,0 +1,149 @@
+/*
+ * Toggles a GPIO pin to restart a device
+ *
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ * Based on the gpio-poweroff driver.
+ */
+#include <linux/reboot.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of_platform.h>
+#include <linux/module.h>
+
+struct gpio_restart {
+ struct gpio_desc *reset_gpio;
+ struct notifier_block restart_handler;
+ u32 active_delay_ms;
+ u32 inactive_delay_ms;
+ u32 wait_delay_ms;
+};
+
+static int gpio_restart_notify(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ struct gpio_restart *gpio_restart =
+ container_of(this, struct gpio_restart, restart_handler);
+
+ /* drive it active, also inactive->active edge */
+ gpiod_direction_output(gpio_restart->reset_gpio, 1);
+ mdelay(gpio_restart->active_delay_ms);
+
+ /* drive inactive, also active->inactive edge */
+ gpiod_set_value(gpio_restart->reset_gpio, 0);
+ mdelay(gpio_restart->inactive_delay_ms);
+
+ /* drive it active, also inactive->active edge */
+ gpiod_set_value(gpio_restart->reset_gpio, 1);
+
+ /* give it some time */
+ mdelay(gpio_restart->wait_delay_ms);
+
+ WARN_ON(1);
+
+ return NOTIFY_DONE;
+}
+
+static int gpio_restart_probe(struct platform_device *pdev)
+{
+ struct gpio_restart *gpio_restart;
+ bool open_source = false;
+ u32 property;
+ int ret;
+
+ gpio_restart = devm_kzalloc(&pdev->dev, sizeof(*gpio_restart),
+ GFP_KERNEL);
+ if (!gpio_restart)
+ return -ENOMEM;
+
+ open_source = of_property_read_bool(pdev->dev.of_node, "open-source");
+
+ gpio_restart->reset_gpio = devm_gpiod_get(&pdev->dev, NULL,
+ open_source ? GPIOD_IN : GPIOD_OUT_LOW);
+ if (IS_ERR(gpio_restart->reset_gpio)) {
+ dev_err(&pdev->dev, "Could net get reset GPIO\n");
+ return PTR_ERR(gpio_restart->reset_gpio);
+ }
+
+ gpio_restart->restart_handler.notifier_call = gpio_restart_notify;
+ gpio_restart->restart_handler.priority = 128;
+ gpio_restart->active_delay_ms = 100;
+ gpio_restart->inactive_delay_ms = 100;
+ gpio_restart->wait_delay_ms = 3000;
+
+ ret = of_property_read_u32(pdev->dev.of_node, "priority", &property);
+ if (!ret) {
+ if (property > 255)
+ dev_err(&pdev->dev, "Invalid priority property: %u\n",
+ property);
+ else
+ gpio_restart->restart_handler.priority = property;
+ }
+
+ of_property_read_u32(pdev->dev.of_node, "active-delay",
+ &gpio_restart->active_delay_ms);
+ of_property_read_u32(pdev->dev.of_node, "inactive-delay",
+ &gpio_restart->inactive_delay_ms);
+ of_property_read_u32(pdev->dev.of_node, "wait-delay",
+ &gpio_restart->wait_delay_ms);
+
+ platform_set_drvdata(pdev, gpio_restart);
+
+ ret = register_restart_handler(&gpio_restart->restart_handler);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: cannot register restart handler, %d\n",
+ __func__, ret);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int gpio_restart_remove(struct platform_device *pdev)
+{
+ struct gpio_restart *gpio_restart = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = unregister_restart_handler(&gpio_restart->restart_handler);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "%s: cannot unregister restart handler, %d\n",
+ __func__, ret);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id of_gpio_restart_match[] = {
+ { .compatible = "gpio-restart", },
+ {},
+};
+
+static struct platform_driver gpio_restart_driver = {
+ .probe = gpio_restart_probe,
+ .remove = gpio_restart_remove,
+ .driver = {
+ .name = "restart-gpio",
+ .owner = THIS_MODULE,
+ .of_match_table = of_gpio_restart_match,
+ },
+};
+
+module_platform_driver(gpio_restart_driver);
+
+MODULE_AUTHOR("David Riley <davidriley@chromium.org>");
+MODULE_DESCRIPTION("GPIO restart driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/reset/ltc2952-poweroff.c b/drivers/power/reset/ltc2952-poweroff.c
new file mode 100644
index 000000000000..116a1cef8f7b
--- /dev/null
+++ b/drivers/power/reset/ltc2952-poweroff.c
@@ -0,0 +1,386 @@
+/*
+ * LTC2952 (PowerPath) driver
+ *
+ * Copyright (C) 2014, Xsens Technologies BV <info@xsens.com>
+ * Maintainer: René Moll <linux@r-moll.nl>
+ *
+ * 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.
+ *
+ * ----------------------------------------
+ * - Description
+ * ----------------------------------------
+ *
+ * This driver is to be used with an external PowerPath Controller (LTC2952).
+ * Its function is to determine when a external shut down is triggered
+ * and react by properly shutting down the system.
+ *
+ * This driver expects a device tree with a ltc2952 entry for pin mapping.
+ *
+ * ----------------------------------------
+ * - GPIO
+ * ----------------------------------------
+ *
+ * The following GPIOs are used:
+ * - trigger (input)
+ * A level change indicates the shut-down trigger. If it's state reverts
+ * within the time-out defined by trigger_delay, the shut down is not
+ * executed.
+ *
+ * - watchdog (output)
+ * Once a shut down is triggered, the driver will toggle this signal,
+ * with an internal (wde_interval) to stall the hardware shut down.
+ *
+ * - kill (output)
+ * The last action during shut down is triggering this signalling, such
+ * that the PowerPath Control will power down the hardware.
+ *
+ * ----------------------------------------
+ * - Interrupts
+ * ----------------------------------------
+ *
+ * The driver requires a non-shared, edge-triggered interrupt on the trigger
+ * GPIO.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/ktime.h>
+#include <linux/slab.h>
+#include <linux/kmod.h>
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/reboot.h>
+
+struct ltc2952_poweroff_data {
+ struct hrtimer timer_trigger;
+ struct hrtimer timer_wde;
+
+ ktime_t trigger_delay;
+ ktime_t wde_interval;
+
+ struct device *dev;
+
+ unsigned int virq;
+
+ /**
+ * 0: trigger
+ * 1: watchdog
+ * 2: kill
+ */
+ struct gpio_desc *gpio[3];
+};
+
+static int ltc2952_poweroff_panic;
+static struct ltc2952_poweroff_data *ltc2952_data;
+
+#define POWERPATH_IO_TRIGGER 0
+#define POWERPATH_IO_WATCHDOG 1
+#define POWERPATH_IO_KILL 2
+
+/**
+ * ltc2952_poweroff_timer_wde - Timer callback
+ * Toggles the watchdog reset signal each wde_interval
+ *
+ * @timer: corresponding timer
+ *
+ * Returns HRTIMER_RESTART for an infinite loop which will only stop when the
+ * machine actually shuts down
+ */
+static enum hrtimer_restart ltc2952_poweroff_timer_wde(struct hrtimer *timer)
+{
+ ktime_t now;
+ int state;
+ unsigned long overruns;
+
+ if (ltc2952_poweroff_panic)
+ return HRTIMER_NORESTART;
+
+ state = gpiod_get_value(ltc2952_data->gpio[POWERPATH_IO_WATCHDOG]);
+ gpiod_set_value(ltc2952_data->gpio[POWERPATH_IO_WATCHDOG], !state);
+
+ now = hrtimer_cb_get_time(timer);
+ overruns = hrtimer_forward(timer, now, ltc2952_data->wde_interval);
+
+ return HRTIMER_RESTART;
+}
+
+static enum hrtimer_restart ltc2952_poweroff_timer_trigger(
+ struct hrtimer *timer)
+{
+ int ret;
+
+ ret = hrtimer_start(&ltc2952_data->timer_wde,
+ ltc2952_data->wde_interval, HRTIMER_MODE_REL);
+
+ if (ret) {
+ dev_err(ltc2952_data->dev, "unable to start the timer\n");
+ /*
+ * The device will not toggle the watchdog reset,
+ * thus shut down is only safe if the PowerPath controller
+ * has a long enough time-off before triggering a hardware
+ * power-off.
+ *
+ * Only sending a warning as the system will power-off anyway
+ */
+ }
+
+ dev_info(ltc2952_data->dev, "executing shutdown\n");
+
+ orderly_poweroff(true);
+
+ return HRTIMER_NORESTART;
+}
+
+/**
+ * ltc2952_poweroff_handler - Interrupt handler
+ * Triggered each time the trigger signal changes state and (de)activates a
+ * time-out (timer_trigger). Once the time-out is actually reached the shut
+ * down is executed.
+ *
+ * @irq: IRQ number
+ * @dev_id: pointer to the main data structure
+ */
+static irqreturn_t ltc2952_poweroff_handler(int irq, void *dev_id)
+{
+ int ret;
+ struct ltc2952_poweroff_data *data = dev_id;
+
+ if (ltc2952_poweroff_panic)
+ goto irq_ok;
+
+ if (hrtimer_active(&data->timer_wde)) {
+ /* shutdown is already triggered, nothing to do any more */
+ goto irq_ok;
+ }
+
+ if (!hrtimer_active(&data->timer_trigger)) {
+ ret = hrtimer_start(&data->timer_trigger, data->trigger_delay,
+ HRTIMER_MODE_REL);
+
+ if (ret)
+ dev_err(data->dev, "unable to start the wait timer\n");
+ } else {
+ ret = hrtimer_cancel(&data->timer_trigger);
+ /* omitting return value check, timer should have been valid */
+ }
+
+irq_ok:
+ return IRQ_HANDLED;
+}
+
+static void ltc2952_poweroff_kill(void)
+{
+ gpiod_set_value(ltc2952_data->gpio[POWERPATH_IO_KILL], 1);
+}
+
+static int ltc2952_poweroff_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ return -ENOSYS;
+}
+
+static int ltc2952_poweroff_resume(struct platform_device *pdev)
+{
+ return -ENOSYS;
+}
+
+static void ltc2952_poweroff_default(struct ltc2952_poweroff_data *data)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(data->gpio); i++)
+ data->gpio[i] = NULL;
+
+ data->wde_interval = ktime_set(0, 300L*1E6L);
+ data->trigger_delay = ktime_set(2, 500L*1E6L);
+
+ hrtimer_init(&data->timer_trigger, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ data->timer_trigger.function = &ltc2952_poweroff_timer_trigger;
+
+ hrtimer_init(&data->timer_wde, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ data->timer_wde.function = &ltc2952_poweroff_timer_wde;
+}
+
+static int ltc2952_poweroff_init(struct platform_device *pdev)
+{
+ int ret, virq;
+ unsigned int i;
+ struct ltc2952_poweroff_data *data;
+
+ static char *name[] = {
+ "trigger",
+ "watchdog",
+ "kill",
+ NULL
+ };
+
+ data = ltc2952_data;
+ ltc2952_poweroff_default(ltc2952_data);
+
+ for (i = 0; i < ARRAY_SIZE(ltc2952_data->gpio); i++) {
+ ltc2952_data->gpio[i] = gpiod_get(&pdev->dev, name[i]);
+
+ if (IS_ERR(ltc2952_data->gpio[i])) {
+ ret = PTR_ERR(ltc2952_data->gpio[i]);
+ dev_err(&pdev->dev,
+ "unable to claim the following gpio: %s\n",
+ name[i]);
+ goto err_io;
+ }
+ }
+
+ ret = gpiod_direction_output(
+ ltc2952_data->gpio[POWERPATH_IO_WATCHDOG], 0);
+ if (ret) {
+ dev_err(&pdev->dev, "unable to use watchdog-gpio as output\n");
+ goto err_io;
+ }
+
+ ret = gpiod_direction_output(ltc2952_data->gpio[POWERPATH_IO_KILL], 0);
+ if (ret) {
+ dev_err(&pdev->dev, "unable to use kill-gpio as output\n");
+ goto err_io;
+ }
+
+ virq = gpiod_to_irq(ltc2952_data->gpio[POWERPATH_IO_TRIGGER]);
+ if (virq < 0) {
+ dev_err(&pdev->dev, "cannot map GPIO as interrupt");
+ goto err_io;
+ }
+
+ ltc2952_data->virq = virq;
+ ret = request_irq(virq,
+ ltc2952_poweroff_handler,
+ (IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING),
+ "ltc2952-poweroff",
+ ltc2952_data
+ );
+
+ if (ret) {
+ dev_err(&pdev->dev, "cannot configure an interrupt handler\n");
+ goto err_io;
+ }
+
+ return 0;
+
+err_io:
+ for (i = 0; i < ARRAY_SIZE(ltc2952_data->gpio); i++)
+ if (ltc2952_data->gpio[i])
+ gpiod_put(ltc2952_data->gpio[i]);
+
+ return ret;
+}
+
+static int ltc2952_poweroff_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ if (pm_power_off) {
+ dev_err(&pdev->dev, "pm_power_off already registered");
+ return -EBUSY;
+ }
+
+ ltc2952_data = kzalloc(sizeof(*ltc2952_data), GFP_KERNEL);
+ if (!ltc2952_data)
+ return -ENOMEM;
+
+ ltc2952_data->dev = &pdev->dev;
+
+ ret = ltc2952_poweroff_init(pdev);
+ if (ret)
+ goto err;
+
+ pm_power_off = &ltc2952_poweroff_kill;
+
+ dev_info(&pdev->dev, "probe successful\n");
+
+ return 0;
+
+err:
+ kfree(ltc2952_data);
+ return ret;
+}
+
+static int ltc2952_poweroff_remove(struct platform_device *pdev)
+{
+ unsigned int i;
+
+ pm_power_off = NULL;
+
+ if (ltc2952_data) {
+ free_irq(ltc2952_data->virq, ltc2952_data);
+
+ for (i = 0; i < ARRAY_SIZE(ltc2952_data->gpio); i++)
+ gpiod_put(ltc2952_data->gpio[i]);
+
+ kfree(ltc2952_data);
+ }
+
+ return 0;
+}
+
+static const struct of_device_id of_ltc2952_poweroff_match[] = {
+ { .compatible = "lltc,ltc2952"},
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_ltc2952_poweroff_match);
+
+static struct platform_driver ltc2952_poweroff_driver = {
+ .probe = ltc2952_poweroff_probe,
+ .remove = ltc2952_poweroff_remove,
+ .driver = {
+ .name = "ltc2952-poweroff",
+ .owner = THIS_MODULE,
+ .of_match_table = of_ltc2952_poweroff_match,
+ },
+ .suspend = ltc2952_poweroff_suspend,
+ .resume = ltc2952_poweroff_resume,
+};
+
+static int ltc2952_poweroff_notify_panic(struct notifier_block *nb,
+ unsigned long code, void *unused)
+{
+ ltc2952_poweroff_panic = 1;
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block ltc2952_poweroff_panic_nb = {
+ .notifier_call = ltc2952_poweroff_notify_panic,
+};
+
+static int __init ltc2952_poweroff_platform_init(void)
+{
+ ltc2952_poweroff_panic = 0;
+
+ atomic_notifier_chain_register(&panic_notifier_list,
+ &ltc2952_poweroff_panic_nb);
+
+ return platform_driver_register(&ltc2952_poweroff_driver);
+}
+
+static void __exit ltc2952_poweroff_platform_exit(void)
+{
+ atomic_notifier_chain_unregister(&panic_notifier_list,
+ &ltc2952_poweroff_panic_nb);
+
+ platform_driver_unregister(&ltc2952_poweroff_driver);
+}
+
+module_init(ltc2952_poweroff_platform_init);
+module_exit(ltc2952_poweroff_platform_exit);
+
+MODULE_AUTHOR("René Moll <rene.moll@xsens.com>");
+MODULE_DESCRIPTION("LTC PowerPath power-off driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/reset/msm-poweroff.c b/drivers/power/reset/msm-poweroff.c
index 774f9a3b310d..4702efdfe466 100644
--- a/drivers/power/reset/msm-poweroff.c
+++ b/drivers/power/reset/msm-poweroff.c
@@ -20,21 +20,27 @@
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/reboot.h>
-
-#include <asm/system_misc.h>
+#include <linux/pm.h>
static void __iomem *msm_ps_hold;
-
-static void do_msm_restart(enum reboot_mode reboot_mode, const char *cmd)
+static int do_msm_restart(struct notifier_block *nb, unsigned long action,
+ void *data)
{
writel(0, msm_ps_hold);
mdelay(10000);
+
+ return NOTIFY_DONE;
}
+static struct notifier_block restart_nb = {
+ .notifier_call = do_msm_restart,
+ .priority = 128,
+};
+
static void do_msm_poweroff(void)
{
/* TODO: Add poweroff capability */
- do_msm_restart(REBOOT_HARD, NULL);
+ do_msm_restart(&restart_nb, 0, NULL);
}
static int msm_restart_probe(struct platform_device *pdev)
@@ -47,8 +53,10 @@ static int msm_restart_probe(struct platform_device *pdev)
if (IS_ERR(msm_ps_hold))
return PTR_ERR(msm_ps_hold);
+ register_restart_handler(&restart_nb);
+
pm_power_off = do_msm_poweroff;
- arm_pm_restart = do_msm_restart;
+
return 0;
}
diff --git a/drivers/power/reset/st-poweroff.c b/drivers/power/reset/st-poweroff.c
new file mode 100644
index 000000000000..a0acf25ee2a2
--- /dev/null
+++ b/drivers/power/reset/st-poweroff.c
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2014 STMicroelectronics
+ *
+ * Power off Restart driver, used in STMicroelectronics devices.
+ *
+ * Author: Christophe Kerello <christophe.kerello@st.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2, as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#include <asm/system_misc.h>
+
+struct reset_syscfg {
+ struct regmap *regmap;
+ /* syscfg used for reset */
+ unsigned int offset_rst;
+ unsigned int mask_rst;
+ /* syscfg used for unmask the reset */
+ unsigned int offset_rst_msk;
+ unsigned int mask_rst_msk;
+};
+
+/* STiH415 */
+#define STIH415_SYSCFG_11 0x2c
+#define STIH415_SYSCFG_15 0x3c
+
+static struct reset_syscfg stih415_reset = {
+ .offset_rst = STIH415_SYSCFG_11,
+ .mask_rst = BIT(0),
+ .offset_rst_msk = STIH415_SYSCFG_15,
+ .mask_rst_msk = BIT(0)
+};
+
+/* STiH416 */
+#define STIH416_SYSCFG_500 0x7d0
+#define STIH416_SYSCFG_504 0x7e0
+
+static struct reset_syscfg stih416_reset = {
+ .offset_rst = STIH416_SYSCFG_500,
+ .mask_rst = BIT(0),
+ .offset_rst_msk = STIH416_SYSCFG_504,
+ .mask_rst_msk = BIT(0)
+};
+
+/* STiH407 */
+#define STIH407_SYSCFG_4000 0x0
+#define STIH407_SYSCFG_4008 0x20
+
+static struct reset_syscfg stih407_reset = {
+ .offset_rst = STIH407_SYSCFG_4000,
+ .mask_rst = BIT(0),
+ .offset_rst_msk = STIH407_SYSCFG_4008,
+ .mask_rst_msk = BIT(0)
+};
+
+/* STiD127 */
+#define STID127_SYSCFG_700 0x0
+#define STID127_SYSCFG_773 0x124
+
+static struct reset_syscfg stid127_reset = {
+ .offset_rst = STID127_SYSCFG_773,
+ .mask_rst = BIT(0),
+ .offset_rst_msk = STID127_SYSCFG_700,
+ .mask_rst_msk = BIT(8)
+};
+
+static struct reset_syscfg *st_restart_syscfg;
+
+static void st_restart(enum reboot_mode reboot_mode, const char *cmd)
+{
+ /* reset syscfg updated */
+ regmap_update_bits(st_restart_syscfg->regmap,
+ st_restart_syscfg->offset_rst,
+ st_restart_syscfg->mask_rst,
+ 0);
+
+ /* unmask the reset */
+ regmap_update_bits(st_restart_syscfg->regmap,
+ st_restart_syscfg->offset_rst_msk,
+ st_restart_syscfg->mask_rst_msk,
+ 0);
+}
+
+static struct of_device_id st_reset_of_match[] = {
+ {
+ .compatible = "st,stih415-restart",
+ .data = (void *)&stih415_reset,
+ }, {
+ .compatible = "st,stih416-restart",
+ .data = (void *)&stih416_reset,
+ }, {
+ .compatible = "st,stih407-restart",
+ .data = (void *)&stih407_reset,
+ }, {
+ .compatible = "st,stid127-restart",
+ .data = (void *)&stid127_reset,
+ },
+ {}
+};
+
+static int st_reset_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ const struct of_device_id *match;
+ struct device *dev = &pdev->dev;
+
+ match = of_match_device(st_reset_of_match, dev);
+ if (!match)
+ return -ENODEV;
+
+ st_restart_syscfg = (struct reset_syscfg *)match->data;
+
+ st_restart_syscfg->regmap =
+ syscon_regmap_lookup_by_phandle(np, "st,syscfg");
+ if (IS_ERR(st_restart_syscfg->regmap)) {
+ dev_err(dev, "No syscfg phandle specified\n");
+ return PTR_ERR(st_restart_syscfg->regmap);
+ }
+
+ arm_pm_restart = st_restart;
+
+ return 0;
+}
+
+static struct platform_driver st_reset_driver = {
+ .probe = st_reset_probe,
+ .driver = {
+ .name = "st_reset",
+ .of_match_table = st_reset_of_match,
+ },
+};
+
+static int __init st_reset_init(void)
+{
+ return platform_driver_register(&st_reset_driver);
+}
+
+device_initcall(st_reset_init);
+
+MODULE_AUTHOR("Christophe Kerello <christophe.kerello@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics Power off Restart driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/reset/syscon-reboot.c b/drivers/power/reset/syscon-reboot.c
new file mode 100644
index 000000000000..815b901822cf
--- /dev/null
+++ b/drivers/power/reset/syscon-reboot.c
@@ -0,0 +1,91 @@
+/*
+ * Generic Syscon Reboot Driver
+ *
+ * Copyright (c) 2013, Applied Micro Circuits Corporation
+ * Author: Feng Kan <fkan@apm.com>
+ *
+ * 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/delay.h>
+#include <linux/io.h>
+#include <linux/notifier.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+
+struct syscon_reboot_context {
+ struct regmap *map;
+ u32 offset;
+ u32 mask;
+ struct notifier_block restart_handler;
+};
+
+static int syscon_restart_handle(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ struct syscon_reboot_context *ctx =
+ container_of(this, struct syscon_reboot_context,
+ restart_handler);
+
+ /* Issue the reboot */
+ regmap_write(ctx->map, ctx->offset, ctx->mask);
+
+ mdelay(1000);
+
+ pr_emerg("Unable to restart system\n");
+ return NOTIFY_DONE;
+}
+
+static int syscon_reboot_probe(struct platform_device *pdev)
+{
+ struct syscon_reboot_context *ctx;
+ struct device *dev = &pdev->dev;
+ int err;
+
+ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->map = syscon_regmap_lookup_by_phandle(dev->of_node, "regmap");
+ if (IS_ERR(ctx->map))
+ return PTR_ERR(ctx->map);
+
+ if (of_property_read_u32(pdev->dev.of_node, "offset", &ctx->offset))
+ return -EINVAL;
+
+ if (of_property_read_u32(pdev->dev.of_node, "mask", &ctx->mask))
+ return -EINVAL;
+
+ ctx->restart_handler.notifier_call = syscon_restart_handle;
+ ctx->restart_handler.priority = 128;
+ err = register_restart_handler(&ctx->restart_handler);
+ if (err)
+ dev_err(dev, "can't register restart notifier (err=%d)\n", err);
+
+ return err;
+}
+
+static struct of_device_id syscon_reboot_of_match[] = {
+ { .compatible = "syscon-reboot" },
+ {}
+};
+
+static struct platform_driver syscon_reboot_driver = {
+ .probe = syscon_reboot_probe,
+ .driver = {
+ .name = "syscon-reboot",
+ .of_match_table = syscon_reboot_of_match,
+ },
+};
+module_platform_driver(syscon_reboot_driver);
diff --git a/drivers/power/reset/xgene-reboot.c b/drivers/power/reset/xgene-reboot.c
index ecd55f81b9d1..6b49be6867ab 100644
--- a/drivers/power/reset/xgene-reboot.c
+++ b/drivers/power/reset/xgene-reboot.c
@@ -40,7 +40,7 @@ struct xgene_reboot_context {
static struct xgene_reboot_context *xgene_restart_ctx;
-static void xgene_restart(char str, const char *cmd)
+static void xgene_restart(enum reboot_mode mode, const char *cmd)
{
struct xgene_reboot_context *ctx = xgene_restart_ctx;
unsigned long timeout;
diff --git a/drivers/power/sbs-battery.c b/drivers/power/sbs-battery.c
index b5f2a76b6cdf..c7b7b4018df3 100644
--- a/drivers/power/sbs-battery.c
+++ b/drivers/power/sbs-battery.c
@@ -48,7 +48,10 @@ enum {
REG_FULL_CHARGE_CAPACITY_CHARGE,
REG_DESIGN_CAPACITY,
REG_DESIGN_CAPACITY_CHARGE,
- REG_DESIGN_VOLTAGE,
+ REG_DESIGN_VOLTAGE_MIN,
+ REG_DESIGN_VOLTAGE_MAX,
+ REG_MANUFACTURER,
+ REG_MODEL_NAME,
};
/* Battery Mode defines */
@@ -68,6 +71,7 @@ enum sbs_battery_mode {
#define BATTERY_FULL_CHARGED 0x20
#define BATTERY_FULL_DISCHARGED 0x10
+/* min_value and max_value are only valid for numerical data */
#define SBS_DATA(_psp, _addr, _min_value, _max_value) { \
.psp = _psp, \
.addr = _addr, \
@@ -111,10 +115,17 @@ static const struct chip_data {
SBS_DATA(POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, 0x18, 0, 65535),
[REG_DESIGN_CAPACITY_CHARGE] =
SBS_DATA(POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 0x18, 0, 65535),
- [REG_DESIGN_VOLTAGE] =
+ [REG_DESIGN_VOLTAGE_MIN] =
+ SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, 0x19, 0, 65535),
+ [REG_DESIGN_VOLTAGE_MAX] =
SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 0x19, 0, 65535),
[REG_SERIAL_NUMBER] =
SBS_DATA(POWER_SUPPLY_PROP_SERIAL_NUMBER, 0x1C, 0, 65535),
+ /* Properties of type `const char *' */
+ [REG_MANUFACTURER] =
+ SBS_DATA(POWER_SUPPLY_PROP_MANUFACTURER, 0x20, 0, 65535),
+ [REG_MODEL_NAME] =
+ SBS_DATA(POWER_SUPPLY_PROP_MODEL_NAME, 0x21, 0, 65535)
};
static enum power_supply_property sbs_properties[] = {
@@ -130,6 +141,7 @@ static enum power_supply_property sbs_properties[] = {
POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
POWER_SUPPLY_PROP_SERIAL_NUMBER,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_ENERGY_NOW,
POWER_SUPPLY_PROP_ENERGY_FULL,
@@ -137,6 +149,9 @@ static enum power_supply_property sbs_properties[] = {
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ /* Properties of type `const char *' */
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_MODEL_NAME
};
struct sbs_info {
@@ -153,6 +168,9 @@ struct sbs_info {
int ignore_changes;
};
+static char model_name[I2C_SMBUS_BLOCK_MAX + 1];
+static char manufacturer[I2C_SMBUS_BLOCK_MAX + 1];
+
static int sbs_read_word_data(struct i2c_client *client, u8 address)
{
struct sbs_info *chip = i2c_get_clientdata(client);
@@ -179,6 +197,74 @@ static int sbs_read_word_data(struct i2c_client *client, u8 address)
return le16_to_cpu(ret);
}
+static int sbs_read_string_data(struct i2c_client *client, u8 address,
+ char *values)
+{
+ struct sbs_info *chip = i2c_get_clientdata(client);
+ s32 ret = 0, block_length = 0;
+ int retries_length = 1, retries_block = 1;
+ u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1];
+
+ if (chip->pdata) {
+ retries_length = max(chip->pdata->i2c_retry_count + 1, 1);
+ retries_block = max(chip->pdata->i2c_retry_count + 1, 1);
+ }
+
+ /* Adapter needs to support these two functions */
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_I2C_BLOCK)){
+ return -ENODEV;
+ }
+
+ /* Get the length of block data */
+ while (retries_length > 0) {
+ ret = i2c_smbus_read_byte_data(client, address);
+ if (ret >= 0)
+ break;
+ retries_length--;
+ }
+
+ if (ret < 0) {
+ dev_dbg(&client->dev,
+ "%s: i2c read at address 0x%x failed\n",
+ __func__, address);
+ return ret;
+ }
+
+ /* block_length does not include NULL terminator */
+ block_length = ret;
+ if (block_length > I2C_SMBUS_BLOCK_MAX) {
+ dev_err(&client->dev,
+ "%s: Returned block_length is longer than 0x%x\n",
+ __func__, I2C_SMBUS_BLOCK_MAX);
+ return -EINVAL;
+ }
+
+ /* Get the block data */
+ while (retries_block > 0) {
+ ret = i2c_smbus_read_i2c_block_data(
+ client, address,
+ block_length + 1, block_buffer);
+ if (ret >= 0)
+ break;
+ retries_block--;
+ }
+
+ if (ret < 0) {
+ dev_dbg(&client->dev,
+ "%s: i2c read at address 0x%x failed\n",
+ __func__, address);
+ return ret;
+ }
+
+ /* block_buffer[0] == block_length */
+ memcpy(values, block_buffer + 1, block_length);
+ values[block_length] = '\0';
+
+ return le16_to_cpu(ret);
+}
+
static int sbs_write_word_data(struct i2c_client *client, u8 address,
u16 value)
{
@@ -318,6 +404,19 @@ static int sbs_get_battery_property(struct i2c_client *client,
return 0;
}
+static int sbs_get_battery_string_property(struct i2c_client *client,
+ int reg_offset, enum power_supply_property psp, char *val)
+{
+ s32 ret;
+
+ ret = sbs_read_string_data(client, sbs_data[reg_offset].addr, val);
+
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
static void sbs_unit_adjustment(struct i2c_client *client,
enum power_supply_property psp, union power_supply_propval *val)
{
@@ -336,6 +435,7 @@ static void sbs_unit_adjustment(struct i2c_client *client,
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
case POWER_SUPPLY_PROP_CURRENT_NOW:
case POWER_SUPPLY_PROP_CHARGE_NOW:
@@ -497,6 +597,7 @@ static int sbs_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_TEMP:
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG:
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
ret = sbs_get_property_index(client, psp);
if (ret < 0)
@@ -505,6 +606,26 @@ static int sbs_get_property(struct power_supply *psy,
ret = sbs_get_battery_property(client, ret, psp, val);
break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ ret = sbs_get_property_index(client, psp);
+ if (ret < 0)
+ break;
+
+ ret = sbs_get_battery_string_property(client, ret, psp,
+ model_name);
+ val->strval = model_name;
+ break;
+
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ ret = sbs_get_property_index(client, psp);
+ if (ret < 0)
+ break;
+
+ ret = sbs_get_battery_string_property(client, ret, psp,
+ manufacturer);
+ val->strval = manufacturer;
+ break;
+
default:
dev_err(&client->dev,
"%s: INVALID property\n", __func__);