power supply and reset changes for the v3.18 series

- 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
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQIcBAABCgAGBQJUOWD7AAoJENju1/PIO/qaj5QP/AtbG96NRpX6Ou8W+qulhSbe
 npTKItRMERZhV+lJUAdmLxQq8Cy6I5Cobdj140oIM6HIKZ7Dh6jChxd2t+RCLsO0
 Y16VMq45L9E/mQuVCLqSSNCPKrRjbF7hW+8gpym/Yc846o3Fv8nhTQzkWxCsVrDR
 rNerRnrUyvKpqRpCN88u4ZuOHjm/146nZtTsAzvL8nGnQBZFJHv4eu7D5nwSN0QS
 FrLiI/mE7h1KFcdK5LrZf51IfEP9w4RVV3vs1wEuP0iPS5dguv2dNzXXVZa5eD/2
 AYSSouVKkgdwryLAvIIaDLD405UYl5Xgt5J6d+QKXxm2+eeZMEY36+PEK5WtRtBX
 CykzO2BgvxyOdz2HU9D8irR1Li71jVSWwQITMJ6bQsbKEQAhltmkmsTOxtqB+ekd
 254rKcKWTqllmEhg1LR8PCidf8OZNEcKzXi4XmQdQIcKNF9tvdTXP5c0/FzuIAlQ
 tqWZW1kQK6/TTfx+XxeeszJWY5VknIaka2Bi83pOZYtu94CtUFBdEVkpaYmC433+
 qLOto7VMy+AKYViTJJDDEnSEFiLNZz/zF+SUDf+YB1RelULSF3zC8CbDcZQa5fNm
 4qTU1fl2gGrIZ1jDPPihm6xP8r9WkeuQWEytG2UJZoa+l4XmzfLHBUUDVo0zqTd3
 txfmKca8Y3GhXGwINi1m
 =sj9S
 -----END PGP SIGNATURE-----

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
This commit is contained in:
Linus Torvalds 2014-10-15 06:56:23 +02:00
commit 50fa86172b
33 changed files with 1919 additions and 249 deletions

View file

@ -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"

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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:

View file

@ -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 = power_supply_register(&pdev->dev, &chg->charger);
ret = device_create_file(&pdev->dev, &dev_attr_fast_charge_timer);
if (ret) {
dev_err(&pdev->dev, "failed: power supply register\n");
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");
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 <[email protected]>");
MODULE_DESCRIPTION("MAXIM 14577 charger driver");
MODULE_DESCRIPTION("Maxim 14577/77836 charger driver");
MODULE_LICENSE("GPL");

View file

@ -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);

View file

@ -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

View file

@ -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);
if (ret) {
dev_dbg(psy->dev, "Failed to find supply, defer!\n");
of_node_put(np);
return -EPROBE_DEFER;
}
of_node_put(np);
if (ret) {
dev_dbg(psy->dev, "Failed to find supply!\n");
return ret;
}
} 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;
}

View file

@ -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)

View file

@ -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),

View file

@ -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

View file

@ -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

View file

@ -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 <[email protected]>");
MODULE_DESCRIPTION("GPIO restart driver");
MODULE_LICENSE("GPL");

View file

@ -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 <[email protected]>");
MODULE_DESCRIPTION("LTC PowerPath power-off driver");
MODULE_LICENSE("GPL v2");

View file

@ -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;
}

View file

@ -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 <[email protected]>");
MODULE_DESCRIPTION("STMicroelectronics Power off Restart driver");
MODULE_LICENSE("GPL v2");

View file

@ -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);

View file

@ -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;

View file

@ -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__);