diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2014-10-15 06:56:23 +0200 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-10-15 06:56:23 +0200 |
commit | 50fa86172bec2769979b5eb0cd1a244391ae4bb0 (patch) | |
tree | 5fd4949b031e1362af8b226d6372da2604de13ff /drivers/power | |
parent | 6b0490816671b2f4126a99998c9bf3c8c0472de2 (diff) | |
parent | 7881c64716f3a7d60b325ed0ad4d15f49b474a43 (diff) | |
download | linux-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/Kconfig | 5 | ||||
-rw-r--r-- | drivers/power/ab8500_fg.c | 9 | ||||
-rw-r--r-- | drivers/power/bq27x00_battery.c | 57 | ||||
-rw-r--r-- | drivers/power/charger-manager.c | 16 | ||||
-rw-r--r-- | drivers/power/gpio-charger.c | 2 | ||||
-rw-r--r-- | drivers/power/max14577_charger.c | 370 | ||||
-rw-r--r-- | drivers/power/max17040_battery.c | 3 | ||||
-rw-r--r-- | drivers/power/max8925_power.c | 7 | ||||
-rw-r--r-- | drivers/power/power_supply_core.c | 100 | ||||
-rw-r--r-- | drivers/power/power_supply_leds.c | 19 | ||||
-rw-r--r-- | drivers/power/power_supply_sysfs.c | 24 | ||||
-rw-r--r-- | drivers/power/reset/Kconfig | 33 | ||||
-rw-r--r-- | drivers/power/reset/Makefile | 4 | ||||
-rw-r--r-- | drivers/power/reset/gpio-restart.c | 149 | ||||
-rw-r--r-- | drivers/power/reset/ltc2952-poweroff.c | 386 | ||||
-rw-r--r-- | drivers/power/reset/msm-poweroff.c | 20 | ||||
-rw-r--r-- | drivers/power/reset/st-poweroff.c | 151 | ||||
-rw-r--r-- | drivers/power/reset/syscon-reboot.c | 91 | ||||
-rw-r--r-- | drivers/power/reset/xgene-reboot.c | 2 | ||||
-rw-r--r-- | drivers/power/sbs-battery.c | 125 |
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, ®_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, ®_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, ®_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, + ®_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(<c2952_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 = <c2952_poweroff_timer_trigger; + + hrtimer_init(&data->timer_wde, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + data->timer_wde.function = <c2952_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 = <c2952_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, + <c2952_poweroff_panic_nb); + + return platform_driver_register(<c2952_poweroff_driver); +} + +static void __exit ltc2952_poweroff_platform_exit(void) +{ + atomic_notifier_chain_unregister(&panic_notifier_list, + <c2952_poweroff_panic_nb); + + platform_driver_unregister(<c2952_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__); |