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 | |
parent | Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/sag... (diff) | |
parent | power: ab8500_fg: Fix build warning (diff) | |
download | linux-50fa86172bec2769979b5eb0cd1a244391ae4bb0.tar.xz linux-50fa86172bec2769979b5eb0cd1a244391ae4bb0.zip |
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
33 files changed, 1919 insertions, 249 deletions
diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index 78c7baca3587..909e7602c717 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -18,3 +18,17 @@ Description: This file is writeable and can be used to set the assumed battery 'full level'. As batteries age, this value has to be amended over time. + +What: /sys/class/power_supply/max14577-charger/device/fast_charge_timer +Date: October 2014 +KernelVersion: 3.18.0 +Contact: Krzysztof Kozlowski <k.kozlowski@samsung.com> +Description: + This entry shows and sets the maximum time the max14577 + charger operates in fast-charge mode. When the timer expires + the device will terminate fast-charge mode (charging current + will drop to 0 A) and will trigger interrupt. + + Valid values: + - 5, 6 or 7 (hours), + - 0: disabled. diff --git a/Documentation/devicetree/bindings/gpio/gpio-restart.txt b/Documentation/devicetree/bindings/gpio/gpio-restart.txt new file mode 100644 index 000000000000..af3701bc15c4 --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/gpio-restart.txt @@ -0,0 +1,54 @@ +Drive a GPIO line that can be used to restart the system from a restart +handler. + +This binding supports level and edge triggered reset. At driver load +time, the driver will request the given gpio line and install a restart +handler. If the optional properties 'open-source' is not found, the GPIO line +will be driven in the inactive state. Otherwise its not driven until +the restart is initiated. + +When the system is restarted, the restart handler will be invoked in +priority order. The gpio is configured as an output, and driven active, +triggering a level triggered reset condition. This will also cause an +inactive->active edge condition, triggering positive edge triggered +reset. After a delay specified by active-delay, the GPIO is set to +inactive, thus causing an active->inactive edge, triggering negative edge +triggered reset. After a delay specified by inactive-delay, the GPIO +is driven active again. After a delay specified by wait-delay, the +restart handler completes allowing other restart handlers to be attempted. + +Required properties: +- compatible : should be "gpio-restart". +- gpios : The GPIO to set high/low, see "gpios property" in + Documentation/devicetree/bindings/gpio/gpio.txt. If the pin should be + low to reset the board set it to "Active Low", otherwise set + gpio to "Active High". + +Optional properties: +- open-source : Treat the GPIO as being open source and defer driving + it to when the restart is initiated. If this optional property is not + specified, the GPIO is initialized as an output in its inactive state. +- priority : A priority ranging from 0 to 255 (default 128) according to + the following guidelines: + 0: Restart handler of last resort, with limited restart + capabilities + 128: Default restart handler; use if no other restart handler is + expected to be available, and/or if restart functionality is + sufficient to restart the entire system + 255: Highest priority restart handler, will preempt all other + restart handlers +- active-delay: Delay (default 100) to wait after driving gpio active [ms] +- inactive-delay: Delay (default 100) to wait after driving gpio inactive [ms] +- wait-delay: Delay (default 3000) to wait after completing restart + sequence [ms] + +Examples: + +gpio-restart { + compatible = "gpio-restart"; + gpios = <&gpio 4 0>; + priority = <128>; + active-delay = <100>; + inactive-delay = <100>; + wait-delay = <3000>; +}; diff --git a/Documentation/devicetree/bindings/mfd/max14577.txt b/Documentation/devicetree/bindings/mfd/max14577.txt new file mode 100644 index 000000000000..236264c10b92 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/max14577.txt @@ -0,0 +1,146 @@ +Maxim MAX14577/77836 Multi-Function Device + +MAX14577 is a Multi-Function Device with Micro-USB Interface Circuit, Li+ +Battery Charger and SFOUT LDO output for powering USB devices. It is +interfaced to host controller using I2C. + +MAX77836 additionally contains PMIC (with two LDO regulators) and Fuel Gauge. + + +Required properties: +- compatible : Must be "maxim,max14577" or "maxim,max77836". +- reg : I2C slave address for the max14577 chip (0x25 for max14577/max77836) +- interrupts : IRQ line for the chip. +- interrupt-parent : The parent interrupt controller. + + +Required nodes: + - charger : + Node for configuring the charger driver. + Required properties: + - compatible : "maxim,max14577-charger" + or "maxim,max77836-charger" + - maxim,fast-charge-uamp : Current in uA for Fast Charge; + Valid values: + - for max14577: 90000 - 950000; + - for max77836: 45000 - 475000; + - maxim,eoc-uamp : Current in uA for End-Of-Charge mode; + Valid values: + - for max14577: 50000 - 200000; + - for max77836: 5000 - 100000; + - maxim,ovp-uvolt : OverVoltage Protection Threshold in uV; + In an overvoltage condition, INT asserts and charging + stops. Valid values: + - 6000000, 6500000, 7000000, 7500000; + - maxim,constant-uvolt : Battery Constant Voltage in uV; + Valid values: + - 4000000 - 4280000 (step by 20000); + - 4350000; + + +Optional nodes: +- max14577-muic/max77836-muic : + Node used only by extcon consumers. + Required properties: + - compatible : "maxim,max14577-muic" or "maxim,max77836-muic" + +- regulators : + Required properties: + - compatible : "maxim,max14577-regulator" + or "maxim,max77836-regulator" + + May contain a sub-node per regulator from the list below. Each + sub-node should contain the constraints and initialization information + for that regulator. See regulator.txt for a description of standard + properties for these sub-nodes. + + List of valid regulator names: + - for max14577: CHARGER, SAFEOUT. + - for max77836: CHARGER, SAFEOUT, LDO1, LDO2. + + The SAFEOUT is a fixed voltage regulator so there is no need to specify + voltages for it. + + +Example: + +#include <dt-bindings/interrupt-controller/irq.h> + +max14577@25 { + compatible = "maxim,max14577"; + reg = <0x25>; + interrupt-parent = <&gpx1>; + interrupts = <5 IRQ_TYPE_NONE>; + + muic: max14577-muic { + compatible = "maxim,max14577-muic"; + }; + + regulators { + compatible = "maxim,max14577-regulator"; + + SAFEOUT { + regulator-name = "SAFEOUT"; + }; + CHARGER { + regulator-name = "CHARGER"; + regulator-min-microamp = <90000>; + regulator-max-microamp = <950000>; + regulator-boot-on; + }; + }; + + charger { + compatible = "maxim,max14577-charger"; + + maxim,constant-uvolt = <4350000>; + maxim,fast-charge-uamp = <450000>; + maxim,eoc-uamp = <50000>; + maxim,ovp-uvolt = <6500000>; + }; +}; + + +max77836@25 { + compatible = "maxim,max77836"; + reg = <0x25>; + interrupt-parent = <&gpx1>; + interrupts = <5 IRQ_TYPE_NONE>; + + muic: max77836-muic { + compatible = "maxim,max77836-muic"; + }; + + regulators { + compatible = "maxim,max77836-regulator"; + + SAFEOUT { + regulator-name = "SAFEOUT"; + }; + CHARGER { + regulator-name = "CHARGER"; + regulator-min-microamp = <90000>; + regulator-max-microamp = <950000>; + regulator-boot-on; + }; + LDO1 { + regulator-name = "LDO1"; + regulator-min-microvolt = <2700000>; + regulator-max-microvolt = <2700000>; + }; + LDO2 { + regulator-name = "LDO2"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <3950000>; + }; + }; + + charger { + compatible = "maxim,max77836-charger"; + + maxim,constant-uvolt = <4350000>; + maxim,fast-charge-uamp = <225000>; + maxim,eoc-uamp = <7500>; + maxim,ovp-uvolt = <6500000>; + }; +}; diff --git a/Documentation/devicetree/bindings/power/reset/ltc2952-poweroff.txt b/Documentation/devicetree/bindings/power/reset/ltc2952-poweroff.txt new file mode 100644 index 000000000000..0c94c637f63b --- /dev/null +++ b/Documentation/devicetree/bindings/power/reset/ltc2952-poweroff.txt @@ -0,0 +1,26 @@ +Binding for the LTC2952 PowerPath controller + +This chip is used to externally trigger a system shut down. Once the trigger has +been sent, the chips' watchdog has to be reset to gracefully shut down. +If the Linux systems decides to shut down it powers off the platform via the +kill signal. + +Required properties: + +- compatible: Must contain: "lltc,ltc2952" +- trigger-gpios: phandle + gpio-specifier for the GPIO connected to the + chip's trigger line +- watchdog-gpios: phandle + gpio-specifier for the GPIO connected to the + chip's watchdog line +- kill-gpios: phandle + gpio-specifier for the GPIO connected to the + chip's kill line + +Example: + +ltc2952 { + compatible = "lltc,ltc2952"; + + trigger-gpios = <&gpio0 1 GPIO_ACTIVE_LOW>; + watchdog-gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>; + kill-gpios = <&gpio0 2 GPIO_ACTIVE_LOW>; +}; diff --git a/Documentation/devicetree/bindings/power/reset/st-reset.txt b/Documentation/devicetree/bindings/power/reset/st-reset.txt new file mode 100644 index 000000000000..809af54f02f3 --- /dev/null +++ b/Documentation/devicetree/bindings/power/reset/st-reset.txt @@ -0,0 +1,11 @@ +*Device-Tree bindings for ST SW reset functionality + +Required properties: +- compatible: should be "st,<chip>-restart". +- st,syscfg: should be a phandle of the syscfg node. + +Example node: + restart { + compatible = "st,stih416-restart"; + st,syscfg = <&syscfg_sbc>; + }; diff --git a/Documentation/devicetree/bindings/power/reset/syscon-reboot.txt b/Documentation/devicetree/bindings/power/reset/syscon-reboot.txt new file mode 100644 index 000000000000..11906316b43d --- /dev/null +++ b/Documentation/devicetree/bindings/power/reset/syscon-reboot.txt @@ -0,0 +1,23 @@ +Generic SYSCON mapped register reset driver + +This is a generic reset driver using syscon to map the reset register. +The reset is generally performed with a write to the reset register +defined by the register map pointed by syscon reference plus the offset +with the mask defined in the reboot node. + +Required properties: +- compatible: should contain "syscon-reboot" +- regmap: this is phandle to the register map node +- offset: offset in the register map for the reboot register (in bytes) +- mask: the reset value written to the reboot register (32 bit access) + +Default will be little endian mode, 32 bit access only. + +Examples: + + reboot { + compatible = "syscon-reboot"; + regmap = <®mapnode>; + offset = <0x0>; + mask = <0x1>; + }; diff --git a/Documentation/power/charger-manager.txt b/Documentation/power/charger-manager.txt index b4f7f4b23f64..9ff1105e58d6 100644 --- a/Documentation/power/charger-manager.txt +++ b/Documentation/power/charger-manager.txt @@ -29,7 +29,7 @@ Charger Manager supports the following: While the battery is being charged and the system is in suspend-to-RAM, we may need to monitor the battery health by looking at the ambient or battery temperature. We can accomplish this by waking up the system - periodically. However, such a method wakes up devices unncessary for + periodically. However, such a method wakes up devices unnecessarily for monitoring the battery health and tasks, and user processes that are supposed to be kept suspended. That, in turn, incurs unnecessary power consumption and slow down charging process. Or even, such peak power diff --git a/Documentation/power/power_supply_class.txt b/Documentation/power/power_supply_class.txt index 48cff881cb8a..82dacc06e355 100644 --- a/Documentation/power/power_supply_class.txt +++ b/Documentation/power/power_supply_class.txt @@ -101,6 +101,10 @@ VOLTAGE_MAX, VOLTAGE_MIN - same as _DESIGN voltage values except that these ones should be used if hardware could only guess (measure and retain) the thresholds of a given power supply. +VOLTAGE_BOOT - Reports the voltage measured during boot + +CURRENT_BOOT - Reports the current measured during boot + CHARGE_FULL_DESIGN, CHARGE_EMPTY_DESIGN - design charge values, when battery considered full/empty. @@ -123,6 +127,8 @@ the current drawn from a charging source. CHARGE_TERM_CURRENT - Charge termination current used to detect the end of charge condition. +CALIBRATE - battery or coulomb counter calibration status + CONSTANT_CHARGE_VOLTAGE - constant charge voltage programmed by charger. CONSTANT_CHARGE_VOLTAGE_MAX - maximum charge voltage supported by the power supply object. diff --git a/drivers/mfd/max14577.c b/drivers/mfd/max14577.c index 4a5e885383f8..b8af263be594 100644 --- a/drivers/mfd/max14577.c +++ b/drivers/mfd/max14577.c @@ -26,6 +26,87 @@ #include <linux/mfd/max14577.h> #include <linux/mfd/max14577-private.h> +/* + * Table of valid charger currents for different Maxim chipsets. + * It is placed here because it is used by both charger and regulator driver. + */ +const struct maxim_charger_current maxim_charger_currents[] = { + [MAXIM_DEVICE_TYPE_UNKNOWN] = { 0, 0, 0, 0 }, + [MAXIM_DEVICE_TYPE_MAX14577] = { + .min = MAX14577_CHARGER_CURRENT_LIMIT_MIN, + .high_start = MAX14577_CHARGER_CURRENT_LIMIT_HIGH_START, + .high_step = MAX14577_CHARGER_CURRENT_LIMIT_HIGH_STEP, + .max = MAX14577_CHARGER_CURRENT_LIMIT_MAX, + }, + [MAXIM_DEVICE_TYPE_MAX77836] = { + .min = MAX77836_CHARGER_CURRENT_LIMIT_MIN, + .high_start = MAX77836_CHARGER_CURRENT_LIMIT_HIGH_START, + .high_step = MAX77836_CHARGER_CURRENT_LIMIT_HIGH_STEP, + .max = MAX77836_CHARGER_CURRENT_LIMIT_MAX, + }, +}; +EXPORT_SYMBOL_GPL(maxim_charger_currents); + +/* + * maxim_charger_calc_reg_current - Calculate register value for current + * @limits: constraints for charger, matching the MBCICHWRC register + * @min_ua: minimal requested current, micro Amps + * @max_ua: maximum requested current, micro Amps + * @dst: destination to store calculated register value + * + * Calculates the value of MBCICHWRC (Fast Battery Charge Current) register + * for given current and stores it under pointed 'dst'. The stored value + * combines low bit (MBCICHWRCL) and high bits (MBCICHWRCH). It is also + * properly shifted. + * + * The calculated register value matches the current which: + * - is always between <limits.min, limits.max>; + * - is always less or equal to max_ua; + * - is the highest possible value; + * - may be lower than min_ua. + * + * On success returns 0. On error returns -EINVAL (requested min/max current + * is outside of given charger limits) and 'dst' is not set. + */ +int maxim_charger_calc_reg_current(const struct maxim_charger_current *limits, + unsigned int min_ua, unsigned int max_ua, u8 *dst) +{ + unsigned int current_bits = 0xf; + + if (min_ua > max_ua) + return -EINVAL; + + if (min_ua > limits->max || max_ua < limits->min) + return -EINVAL; + + if (max_ua < limits->high_start) { + /* + * Less than high_start, so set the minimal current + * (turn Low Bit off, 0 as high bits). + */ + *dst = 0x0; + return 0; + } + + /* max_ua is in range: <high_start, infinite>, cut it to limits.max */ + max_ua = min(limits->max, max_ua); + max_ua -= limits->high_start; + /* + * There is no risk of overflow 'max_ua' here because: + * - max_ua >= limits.high_start + * - BUILD_BUG checks that 'limits' are: max >= high_start + high_step + */ + current_bits = max_ua / limits->high_step; + + /* Turn Low Bit on (use range <limits.high_start, limits.max>) ... */ + *dst = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT; + /* and set proper High Bits */ + *dst |= current_bits << CHGCTRL4_MBCICHWRCH_SHIFT; + + return 0; +} +EXPORT_SYMBOL_GPL(maxim_charger_calc_reg_current); + static const struct mfd_cell max14577_devs[] = { { .name = "max14577-muic", @@ -35,7 +116,10 @@ static const struct mfd_cell max14577_devs[] = { .name = "max14577-regulator", .of_compatible = "maxim,max14577-regulator", }, - { .name = "max14577-charger", }, + { + .name = "max14577-charger", + .of_compatible = "maxim,max14577-charger", + }, }; static const struct mfd_cell max77836_devs[] = { @@ -463,6 +547,20 @@ static int __init max14577_i2c_init(void) BUILD_BUG_ON(ARRAY_SIZE(max14577_i2c_id) != MAXIM_DEVICE_TYPE_NUM); BUILD_BUG_ON(ARRAY_SIZE(max14577_dt_match) != MAXIM_DEVICE_TYPE_NUM); + /* Valid charger current values must be provided for each chipset */ + BUILD_BUG_ON(ARRAY_SIZE(maxim_charger_currents) != MAXIM_DEVICE_TYPE_NUM); + + /* Check for valid values for charger */ + BUILD_BUG_ON(MAX14577_CHARGER_CURRENT_LIMIT_HIGH_START + + MAX14577_CHARGER_CURRENT_LIMIT_HIGH_STEP * 0xf != + MAX14577_CHARGER_CURRENT_LIMIT_MAX); + BUILD_BUG_ON(MAX14577_CHARGER_CURRENT_LIMIT_HIGH_STEP == 0); + + BUILD_BUG_ON(MAX77836_CHARGER_CURRENT_LIMIT_HIGH_START + + MAX77836_CHARGER_CURRENT_LIMIT_HIGH_STEP * 0xf != + MAX77836_CHARGER_CURRENT_LIMIT_MAX); + BUILD_BUG_ON(MAX77836_CHARGER_CURRENT_LIMIT_HIGH_STEP == 0); + return i2c_add_driver(&max14577_i2c_driver); } subsys_initcall(max14577_i2c_init); 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__); diff --git a/drivers/regulator/max14577.c b/drivers/regulator/max14577.c index 5d9c605cf534..0ff5a20ac958 100644 --- a/drivers/regulator/max14577.c +++ b/drivers/regulator/max14577.c @@ -22,42 +22,6 @@ #include <linux/mfd/max14577-private.h> #include <linux/regulator/of_regulator.h> -/* - * Valid limits of current for max14577 and max77836 chargers. - * They must correspond to MBCICHWRCL and MBCICHWRCH fields in CHGCTRL4 - * register for given chipset. - */ -struct maxim_charger_current { - /* Minimal current, set in CHGCTRL4/MBCICHWRCL, uA */ - unsigned int min; - /* - * Minimal current when high setting is active, - * set in CHGCTRL4/MBCICHWRCH, uA - */ - unsigned int high_start; - /* Value of one step in high setting, uA */ - unsigned int high_step; - /* Maximum current of high setting, uA */ - unsigned int max; -}; - -/* Table of valid charger currents for different Maxim chipsets */ -static const struct maxim_charger_current maxim_charger_currents[] = { - [MAXIM_DEVICE_TYPE_UNKNOWN] = { 0, 0, 0, 0 }, - [MAXIM_DEVICE_TYPE_MAX14577] = { - .min = MAX14577_REGULATOR_CURRENT_LIMIT_MIN, - .high_start = MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START, - .high_step = MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP, - .max = MAX14577_REGULATOR_CURRENT_LIMIT_MAX, - }, - [MAXIM_DEVICE_TYPE_MAX77836] = { - .min = MAX77836_REGULATOR_CURRENT_LIMIT_MIN, - .high_start = MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_START, - .high_step = MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_STEP, - .max = MAX77836_REGULATOR_CURRENT_LIMIT_MAX, - }, -}; - static int max14577_reg_is_enabled(struct regulator_dev *rdev) { int rid = rdev_get_id(rdev); @@ -103,8 +67,8 @@ static int max14577_reg_get_current_limit(struct regulator_dev *rdev) static int max14577_reg_set_current_limit(struct regulator_dev *rdev, int min_uA, int max_uA) { - int i, current_bits = 0xf; u8 reg_data; + int ret; struct max14577 *max14577 = rdev_get_drvdata(rdev); const struct maxim_charger_current *limits = &maxim_charger_currents[max14577->dev_type]; @@ -112,35 +76,9 @@ static int max14577_reg_set_current_limit(struct regulator_dev *rdev, if (rdev_get_id(rdev) != MAX14577_CHARGER) return -EINVAL; - if (min_uA > limits->max || max_uA < limits->min) - return -EINVAL; - - if (max_uA < limits->high_start) { - /* - * Less than high_start, - * so set the minimal current (turn only Low Bit off) - */ - u8 reg_data = 0x0 << CHGCTRL4_MBCICHWRCL_SHIFT; - return max14577_update_reg(rdev->regmap, - MAX14577_CHG_REG_CHG_CTRL4, - CHGCTRL4_MBCICHWRCL_MASK, reg_data); - } - - /* - * max_uA is in range: <high_start, inifinite>, so search for - * valid current starting from maximum current. - */ - for (i = limits->max; i >= limits->high_start; i -= limits->high_step) { - if (i <= max_uA) - break; - current_bits--; - } - BUG_ON(current_bits < 0); /* Cannot happen */ - - /* Turn Low Bit on (use range high_start-max)... */ - reg_data = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT; - /* and set proper High Bits */ - reg_data |= current_bits << CHGCTRL4_MBCICHWRCH_SHIFT; + ret = maxim_charger_calc_reg_current(limits, min_uA, max_uA, ®_data); + if (ret) + return ret; return max14577_update_reg(rdev->regmap, MAX14577_CHG_REG_CHG_CTRL4, CHGCTRL4_MBCICHWRCL_MASK | CHGCTRL4_MBCICHWRCH_MASK, @@ -442,16 +380,6 @@ static struct platform_driver max14577_regulator_driver = { static int __init max14577_regulator_init(void) { - /* Check for valid values for charger */ - BUILD_BUG_ON(MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START + - MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP * 0xf != - MAX14577_REGULATOR_CURRENT_LIMIT_MAX); - BUILD_BUG_ON(MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_START + - MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_STEP * 0xf != - MAX77836_REGULATOR_CURRENT_LIMIT_MAX); - /* Valid charger current values must be provided for each chipset */ - BUILD_BUG_ON(ARRAY_SIZE(maxim_charger_currents) != MAXIM_DEVICE_TYPE_NUM); - BUILD_BUG_ON(ARRAY_SIZE(max14577_supported_regulators) != MAX14577_REGULATOR_NUM); BUILD_BUG_ON(ARRAY_SIZE(max77836_supported_regulators) != MAX77836_REGULATOR_NUM); diff --git a/include/linux/mfd/max14577-private.h b/include/linux/mfd/max14577-private.h index 499253604026..f01c1fae4d84 100644 --- a/include/linux/mfd/max14577-private.h +++ b/include/linux/mfd/max14577-private.h @@ -72,15 +72,33 @@ enum max14577_muic_reg { MAX14577_MUIC_REG_END, }; +/* + * Combined charger types for max14577 and max77836. + * + * On max14577 three lower bits map to STATUS2/CHGTYP field. + * However the max77836 has different two last values of STATUS2/CHGTYP. + * To indicate the difference enum has two additional values for max77836. + * These values are just a register value bitwise OR with 0x8. + */ enum max14577_muic_charger_type { - MAX14577_CHARGER_TYPE_NONE = 0, - MAX14577_CHARGER_TYPE_USB, - MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT, - MAX14577_CHARGER_TYPE_DEDICATED_CHG, - MAX14577_CHARGER_TYPE_SPECIAL_500MA, - MAX14577_CHARGER_TYPE_SPECIAL_1A, - MAX14577_CHARGER_TYPE_RESERVED, - MAX14577_CHARGER_TYPE_DEAD_BATTERY = 7, + MAX14577_CHARGER_TYPE_NONE = 0x0, + MAX14577_CHARGER_TYPE_USB = 0x1, + MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT = 0x2, + MAX14577_CHARGER_TYPE_DEDICATED_CHG = 0x3, + MAX14577_CHARGER_TYPE_SPECIAL_500MA = 0x4, + /* Special 1A or 2A charger */ + MAX14577_CHARGER_TYPE_SPECIAL_1A = 0x5, + /* max14577: reserved, used on max77836 */ + MAX14577_CHARGER_TYPE_RESERVED = 0x6, + /* max14577: dead-battery charing with maximum current 100mA */ + MAX14577_CHARGER_TYPE_DEAD_BATTERY = 0x7, + /* + * max77836: special charger (bias on D+/D-), + * matches register value of 0x6 + */ + MAX77836_CHARGER_TYPE_SPECIAL_BIAS = 0xe, + /* max77836: reserved, register value 0x7 */ + MAX77836_CHARGER_TYPE_RESERVED = 0xf, }; /* MAX14577 interrupts */ @@ -121,13 +139,15 @@ enum max14577_muic_charger_type { #define STATUS2_CHGTYP_SHIFT 0 #define STATUS2_CHGDETRUN_SHIFT 3 #define STATUS2_DCDTMR_SHIFT 4 -#define STATUS2_DBCHG_SHIFT 5 +#define MAX14577_STATUS2_DBCHG_SHIFT 5 +#define MAX77836_STATUS2_DXOVP_SHIFT 5 #define STATUS2_VBVOLT_SHIFT 6 #define MAX77836_STATUS2_VIDRM_SHIFT 7 #define STATUS2_CHGTYP_MASK (0x7 << STATUS2_CHGTYP_SHIFT) #define STATUS2_CHGDETRUN_MASK BIT(STATUS2_CHGDETRUN_SHIFT) #define STATUS2_DCDTMR_MASK BIT(STATUS2_DCDTMR_SHIFT) -#define STATUS2_DBCHG_MASK BIT(STATUS2_DBCHG_SHIFT) +#define MAX14577_STATUS2_DBCHG_MASK BIT(MAX14577_STATUS2_DBCHG_SHIFT) +#define MAX77836_STATUS2_DXOVP_MASK BIT(MAX77836_STATUS2_DXOVP_SHIFT) #define STATUS2_VBVOLT_MASK BIT(STATUS2_VBVOLT_SHIFT) #define MAX77836_STATUS2_VIDRM_MASK BIT(MAX77836_STATUS2_VIDRM_SHIFT) @@ -177,9 +197,11 @@ enum max14577_muic_charger_type { #define CTRL3_JIGSET_SHIFT 0 #define CTRL3_BOOTSET_SHIFT 2 #define CTRL3_ADCDBSET_SHIFT 4 +#define CTRL3_WBTH_SHIFT 6 #define CTRL3_JIGSET_MASK (0x3 << CTRL3_JIGSET_SHIFT) #define CTRL3_BOOTSET_MASK (0x3 << CTRL3_BOOTSET_SHIFT) #define CTRL3_ADCDBSET_MASK (0x3 << CTRL3_ADCDBSET_SHIFT) +#define CTRL3_WBTH_MASK (0x3 << CTRL3_WBTH_SHIFT) /* Slave addr = 0x4A: Charger */ enum max14577_charger_reg { @@ -210,16 +232,20 @@ enum max14577_charger_reg { #define CDETCTRL1_CHGTYPMAN_SHIFT 1 #define CDETCTRL1_DCDEN_SHIFT 2 #define CDETCTRL1_DCD2SCT_SHIFT 3 -#define CDETCTRL1_DCHKTM_SHIFT 4 -#define CDETCTRL1_DBEXIT_SHIFT 5 +#define MAX14577_CDETCTRL1_DCHKTM_SHIFT 4 +#define MAX77836_CDETCTRL1_CDLY_SHIFT 4 +#define MAX14577_CDETCTRL1_DBEXIT_SHIFT 5 +#define MAX77836_CDETCTRL1_DCDCPL_SHIFT 5 #define CDETCTRL1_DBIDLE_SHIFT 6 #define CDETCTRL1_CDPDET_SHIFT 7 #define CDETCTRL1_CHGDETEN_MASK BIT(CDETCTRL1_CHGDETEN_SHIFT) #define CDETCTRL1_CHGTYPMAN_MASK BIT(CDETCTRL1_CHGTYPMAN_SHIFT) #define CDETCTRL1_DCDEN_MASK BIT(CDETCTRL1_DCDEN_SHIFT) #define CDETCTRL1_DCD2SCT_MASK BIT(CDETCTRL1_DCD2SCT_SHIFT) -#define CDETCTRL1_DCHKTM_MASK BIT(CDETCTRL1_DCHKTM_SHIFT) -#define CDETCTRL1_DBEXIT_MASK BIT(CDETCTRL1_DBEXIT_SHIFT) +#define MAX14577_CDETCTRL1_DCHKTM_MASK BIT(MAX14577_CDETCTRL1_DCHKTM_SHIFT) +#define MAX77836_CDETCTRL1_CDDLY_MASK BIT(MAX77836_CDETCTRL1_CDDLY_SHIFT) +#define MAX14577_CDETCTRL1_DBEXIT_MASK BIT(MAX14577_CDETCTRL1_DBEXIT_SHIFT) +#define MAX77836_CDETCTRL1_DCDCPL_MASK BIT(MAX77836_CDETCTRL1_DCDCPL_SHIFT) #define CDETCTRL1_DBIDLE_MASK BIT(CDETCTRL1_DBIDLE_SHIFT) #define CDETCTRL1_CDPDET_MASK BIT(CDETCTRL1_CDPDET_SHIFT) @@ -255,17 +281,36 @@ enum max14577_charger_reg { #define CHGCTRL7_OTPCGHCVS_SHIFT 0 #define CHGCTRL7_OTPCGHCVS_MASK (0x3 << CHGCTRL7_OTPCGHCVS_SHIFT) -/* MAX14577 regulator current limits (as in CHGCTRL4 register), uA */ -#define MAX14577_REGULATOR_CURRENT_LIMIT_MIN 90000 -#define MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START 200000 -#define MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP 50000 -#define MAX14577_REGULATOR_CURRENT_LIMIT_MAX 950000 - -/* MAX77836 regulator current limits (as in CHGCTRL4 register), uA */ -#define MAX77836_REGULATOR_CURRENT_LIMIT_MIN 45000 -#define MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_START 100000 -#define MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_STEP 25000 -#define MAX77836_REGULATOR_CURRENT_LIMIT_MAX 475000 +/* MAX14577 charger current limits (as in CHGCTRL4 register), uA */ +#define MAX14577_CHARGER_CURRENT_LIMIT_MIN 90000U +#define MAX14577_CHARGER_CURRENT_LIMIT_HIGH_START 200000U +#define MAX14577_CHARGER_CURRENT_LIMIT_HIGH_STEP 50000U +#define MAX14577_CHARGER_CURRENT_LIMIT_MAX 950000U + +/* MAX77836 charger current limits (as in CHGCTRL4 register), uA */ +#define MAX77836_CHARGER_CURRENT_LIMIT_MIN 45000U +#define MAX77836_CHARGER_CURRENT_LIMIT_HIGH_START 100000U +#define MAX77836_CHARGER_CURRENT_LIMIT_HIGH_STEP 25000U +#define MAX77836_CHARGER_CURRENT_LIMIT_MAX 475000U + +/* + * MAX14577 charger End-Of-Charge current limits + * (as in CHGCTRL5 register), uA + */ +#define MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN 50000U +#define MAX14577_CHARGER_EOC_CURRENT_LIMIT_STEP 10000U +#define MAX14577_CHARGER_EOC_CURRENT_LIMIT_MAX 200000U + +/* + * MAX14577/MAX77836 Battery Constant Voltage + * (as in CHGCTRL3 register), uV + */ +#define MAXIM_CHARGER_CONSTANT_VOLTAGE_MIN 4000000U +#define MAXIM_CHARGER_CONSTANT_VOLTAGE_STEP 20000U +#define MAXIM_CHARGER_CONSTANT_VOLTAGE_MAX 4350000U + +/* Default value for fast charge timer, in hours */ +#define MAXIM_CHARGER_FAST_CHARGE_TIMER_DEFAULT 5 /* MAX14577 regulator SFOUT LDO voltage, fixed, uV */ #define MAX14577_REGULATOR_SAFEOUT_VOLTAGE 4900000 diff --git a/include/linux/mfd/max14577.h b/include/linux/mfd/max14577.h index c83fbed1c7b6..ccfaf952c31b 100644 --- a/include/linux/mfd/max14577.h +++ b/include/linux/mfd/max14577.h @@ -54,6 +54,13 @@ struct max14577_regulator_platform_data { struct device_node *of_node; }; +struct max14577_charger_platform_data { + u32 constant_uvolt; + u32 fast_charge_uamp; + u32 eoc_uamp; + u32 ovp_uvolt; +}; + /* * MAX14577 MFD platform data */ @@ -74,4 +81,27 @@ struct max14577_platform_data { struct max14577_regulator_platform_data *regulators; }; +/* + * Valid limits of current for max14577 and max77836 chargers. + * They must correspond to MBCICHWRCL and MBCICHWRCH fields in CHGCTRL4 + * register for given chipset. + */ +struct maxim_charger_current { + /* Minimal current, set in CHGCTRL4/MBCICHWRCL, uA */ + unsigned int min; + /* + * Minimal current when high setting is active, + * set in CHGCTRL4/MBCICHWRCH, uA + */ + unsigned int high_start; + /* Value of one step in high setting, uA */ + unsigned int high_step; + /* Maximum current of high setting, uA */ + unsigned int max; +}; + +extern const struct maxim_charger_current maxim_charger_currents[]; +extern int maxim_charger_calc_reg_current(const struct maxim_charger_current *limits, + unsigned int min_ua, unsigned int max_ua, u8 *dst); + #endif /* __MAX14577_H__ */ diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index f3dea41dbcd2..3ed049673022 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -18,8 +18,6 @@ #include <linux/spinlock.h> #include <linux/notifier.h> -struct device; - /* * All voltages, currents, charges, energies, time and temperatures in uV, * µA, µAh, µWh, seconds and tenths of degree Celsius unless otherwise @@ -102,9 +100,11 @@ enum power_supply_property { POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_VOLTAGE_AVG, POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_VOLTAGE_BOOT, POWER_SUPPLY_PROP_CURRENT_MAX, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CURRENT_BOOT, POWER_SUPPLY_PROP_POWER_NOW, POWER_SUPPLY_PROP_POWER_AVG, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, @@ -146,6 +146,7 @@ enum power_supply_property { POWER_SUPPLY_PROP_TYPE, /* use power_supply.type instead */ POWER_SUPPLY_PROP_SCOPE, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, + POWER_SUPPLY_PROP_CALIBRATE, /* Properties of type `const char *' */ POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_MANUFACTURER, @@ -172,6 +173,7 @@ union power_supply_propval { const char *strval; }; +struct device; struct device_node; struct power_supply { @@ -291,6 +293,7 @@ static inline bool power_supply_is_amp_property(enum power_supply_property psp) case POWER_SUPPLY_PROP_CURRENT_MAX: case POWER_SUPPLY_PROP_CURRENT_NOW: case POWER_SUPPLY_PROP_CURRENT_AVG: + case POWER_SUPPLY_PROP_CURRENT_BOOT: return 1; default: break; @@ -315,6 +318,7 @@ static inline bool power_supply_is_watt_property(enum power_supply_property psp) case POWER_SUPPLY_PROP_VOLTAGE_NOW: case POWER_SUPPLY_PROP_VOLTAGE_AVG: case POWER_SUPPLY_PROP_VOLTAGE_OCV: + case POWER_SUPPLY_PROP_VOLTAGE_BOOT: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: case POWER_SUPPLY_PROP_POWER_NOW: |