Commit 3a2a8751 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'for-v5.8' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply

Pull power supply and reset updates from Sebastian Reichel:
 "This time there are lots of changes. Quite a few changes to the core,
  lots of driver changes and one change to kobject core (with Ack from
  Greg).

  Summary:

  kobject:
   - Increase number of allowed uevent variables

  power-supply core:
   - Add power-supply type in uevent
   - Cleanup property handling in core
   - Make property and usb_type pointers const
   - Convert core power-supply DT binding to YAML
   - Cleanup HWMON code
   - Add new health status "calibration required"
   - Add new properties for manufacture date and capacity error margin

  battery drivers:
   - new cw2015 battery driver used by pine64 Pinebook Pro laptop
   - axp22: blacklist on Meegopad T02
   - sc27xx: support current/voltage reading
   - max17042: support time-to-empty reading
   - simple-battery: add more battery parameters
   - bq27xxx: convert DT binding document to YAML
   - sbs-battery: add TI BQ20Z65 support, fix technology property,
         convert DT binding to YAML, add option to disable charger
         broadcasts, add new properties: manufacture date, capacity
         error margin, average current, charge current and voltage and
         support calibration required health status
   - misc fixes

  charger drivers:
   - bq25890: cleanup, implement charge type, precharge current and
         input current limiting properties
   - bd70528: use new linear range helper library
   - bd99954: new charger driver
   - mp2629: new charger driver
   - misc fixes

  reboot drivers:
   - oxnas-restart: introduce new driver
   - syscon-reboot: convert DT binding to YAML, add parent syscon device
         support
   - misc fixes"

* tag 'for-v5.8' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (85 commits)
  power: supply: cw2015: Attach OF ID table to the driver
  power: reset: gpio-poweroff: add missing '\n' in dev_err()
  Revert "power: supply: sbs-battery: simplify read_read_string_data"
  Revert "power: supply: sbs-battery: add PEC support"
  dt-bindings: power: sbs-battery: Convert to yaml
  power: supply: sbs-battery: constify power-supply property array
  power: supply: sbs-battery: switch to i2c's probe_new
  power: supply: sbs-battery: switch from of_property_* to device_property_*
  power: supply: sbs-battery: add ability to disable charger broadcasts
  power: supply: sbs-battery: fix idle battery status
  power: supply: sbs-battery: add POWER_SUPPLY_HEALTH_CALIBRATION_REQUIRED support
  power: supply: sbs-battery: add MANUFACTURE_DATE support
  power: supply: sbs-battery: add POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT/VOLTAGE_MAX support
  power: supply: sbs-battery: Improve POWER_SUPPLY_PROP_TECHNOLOGY support
  power: supply: sbs-battery: add POWER_SUPPLY_PROP_CURRENT_AVG support
  power: supply: sbs-battery: add PEC support
  power: supply: sbs-battery: simplify read_read_string_data
  power: supply: sbs-battery: add POWER_SUPPLY_PROP_CAPACITY_ERROR_MARGIN support
  power: supply: sbs-battery: Add TI BQ20Z65 support
  power: supply: core: add POWER_SUPPLY_HEALTH_CALIBRATION_REQUIRED
  ...
parents c90e7945 152204db
...@@ -74,6 +74,21 @@ Description: ...@@ -74,6 +74,21 @@ Description:
Access: Read, Write Access: Read, Write
Valid values: 0 - 100 (percent) Valid values: 0 - 100 (percent)
What: /sys/class/power_supply/<supply_name>/capacity_error_margin
Date: April 2019
Contact: linux-pm@vger.kernel.org
Description:
Battery capacity measurement becomes unreliable without
recalibration. This values provides the maximum error
margin expected to exist by the fuel gauge in percent.
Values close to 0% will be returned after (re-)calibration
has happened. Over time the error margin will increase.
100% means, that the capacity related values are basically
completely useless.
Access: Read
Valid values: 0 - 100 (percent)
What: /sys/class/power_supply/<supply_name>/capacity_level What: /sys/class/power_supply/<supply_name>/capacity_level
Date: June 2009 Date: June 2009
Contact: linux-pm@vger.kernel.org Contact: linux-pm@vger.kernel.org
...@@ -190,7 +205,7 @@ Description: ...@@ -190,7 +205,7 @@ Description:
Valid values: "Unknown", "Good", "Overheat", "Dead", Valid values: "Unknown", "Good", "Overheat", "Dead",
"Over voltage", "Unspecified failure", "Cold", "Over voltage", "Unspecified failure", "Cold",
"Watchdog timer expire", "Safety timer expire", "Watchdog timer expire", "Safety timer expire",
"Over current" "Over current", "Calibration required"
What: /sys/class/power_supply/<supply_name>/precharge_current What: /sys/class/power_supply/<supply_name>/precharge_current
Date: June 2017 Date: June 2017
...@@ -665,3 +680,31 @@ Description: ...@@ -665,3 +680,31 @@ Description:
Valid values: Valid values:
- 1: enabled - 1: enabled
- 0: disabled - 0: disabled
What: /sys/class/power_supply/<supply_name>/manufacture_year
Date: January 2020
Contact: linux-pm@vger.kernel.org
Description:
Reports the year (following Gregorian calendar) when the device has been
manufactured.
Access: Read
Valid values: Reported as integer
What: /sys/class/power_supply/<supply_name>/manufacture_month
Date: January 2020
Contact: linux-pm@vger.kernel.org
Description:
Reports the month when the device has been manufactured.
Access: Read
Valid values: 1-12
What: /sys/class/power_supply/<supply_name>/manufacture_day
Date: January 2020
Contact: linux-pm@vger.kernel.org
Description:
Reports the day of month when the device has been manufactured.
Access: Read
Valid values: 1-31
SYSCON reboot mode driver
This driver gets reboot mode magic value form reboot-mode driver
and stores it in a SYSCON mapped register. Then the bootloader
can read it and take different action according to the magic
value stored.
This DT node should be represented as a sub-node of a "syscon", "simple-mfd"
node.
Required properties:
- compatible: should be "syscon-reboot-mode"
- offset: offset in the register map for the storage register (in bytes)
Optional property:
- mask: bits mask of the bits in the register to store the reboot mode magic value,
default set to 0xffffffff if missing.
The rest of the properties should follow the generic reboot-mode description
found in reboot-mode.txt
Example:
pmu: pmu@20004000 {
compatible = "rockchip,rk3066-pmu", "syscon", "simple-mfd";
reg = <0x20004000 0x100>;
reboot-mode {
compatible = "syscon-reboot-mode";
offset = <0x40>;
mode-normal = <BOOT_NORMAL>;
mode-recovery = <BOOT_RECOVERY>;
mode-bootloader = <BOOT_FASTBOOT>;
mode-loader = <BOOT_BL_DOWNLOAD>;
};
};
# SPDX-License-Identifier: GPL-2.0-only
%YAML 1.2
---
$id: http://devicetree.org/schemas/power/reset/syscon-reboot-mode.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Generic SYSCON reboot mode driver
maintainers:
- Sebastian Reichel <sre@kernel.org>
description: |
This driver gets reboot mode magic value from reboot-mode driver
and stores it in a SYSCON mapped register. Then the bootloader
can read it and take different action according to the magic
value stored. The SYSCON mapped register is retrieved from the
parental dt-node plus the offset. So the SYSCON reboot-mode node
should be represented as a sub-node of a "syscon", "simple-mfd" node.
properties:
compatible:
const: syscon-reboot-mode
mask:
$ref: /schemas/types.yaml#/definitions/uint32
description: Update only the register bits defined by the mask (32 bit)
offset:
$ref: /schemas/types.yaml#/definitions/uint32
description: Offset in the register map for the mode register (in bytes)
patternProperties:
"^mode-.+":
$ref: /schemas/types.yaml#/definitions/uint32
description: Vendor-specific mode value written to the mode register
additionalProperties: false
required:
- compatible
- offset
examples:
- |
#include <dt-bindings/soc/rockchip,boot-mode.h>
reboot-mode {
compatible = "syscon-reboot-mode";
offset = <0x40>;
mode-normal = <BOOT_NORMAL>;
mode-recovery = <BOOT_RECOVERY>;
mode-bootloader = <BOOT_FASTBOOT>;
mode-loader = <BOOT_BL_DOWNLOAD>;
};
...
...@@ -12,9 +12,12 @@ maintainers: ...@@ -12,9 +12,12 @@ maintainers:
description: |+ description: |+
This is a generic reset driver using syscon to map the reset register. 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 The reset is generally performed with a write to the reset register
defined by the register map pointed by syscon reference plus the offset defined by the SYSCON register map base plus the offset with the value and
with the value and mask defined in the reboot node. mask defined in the reboot node. Default will be little endian mode, 32 bit
Default will be little endian mode, 32 bit access only. access only. The SYSCON registers map is normally retrieved from the
parental dt-node. So the SYSCON reboot node should be represented as a
sub-node of a "syscon", "simple-mfd" node. Though the regmap property
pointing to the system controller node is also supported.
properties: properties:
compatible: compatible:
...@@ -30,7 +33,10 @@ properties: ...@@ -30,7 +33,10 @@ properties:
regmap: regmap:
$ref: /schemas/types.yaml#/definitions/phandle $ref: /schemas/types.yaml#/definitions/phandle
description: Phandle to the register map node. deprecated: true
description: |
Phandle to the register map node. This property is deprecated in favor of
the syscon-reboot node been a child of a system controller node.
value: value:
$ref: /schemas/types.yaml#/definitions/uint32 $ref: /schemas/types.yaml#/definitions/uint32
...@@ -38,7 +44,6 @@ properties: ...@@ -38,7 +44,6 @@ properties:
required: required:
- compatible - compatible
- regmap
- offset - offset
additionalProperties: false additionalProperties: false
......
...@@ -11,15 +11,21 @@ different type. This prevents unpredictable, potentially harmful, ...@@ -11,15 +11,21 @@ different type. This prevents unpredictable, potentially harmful,
behavior should a replacement that changes the battery type occur behavior should a replacement that changes the battery type occur
without a corresponding update to the dtb. without a corresponding update to the dtb.
Please note that not all charger drivers respect all of the properties.
Required Properties: Required Properties:
- compatible: Must be "simple-battery" - compatible: Must be "simple-battery"
Optional Properties: Optional Properties:
- over-voltage-threshold-microvolt: battery over-voltage limit
- re-charge-voltage-microvolt: limit to automatically start charging again
- voltage-min-design-microvolt: drained battery voltage - voltage-min-design-microvolt: drained battery voltage
- voltage-max-design-microvolt: fully charged battery voltage - voltage-max-design-microvolt: fully charged battery voltage
- energy-full-design-microwatt-hours: battery design energy - energy-full-design-microwatt-hours: battery design energy
- charge-full-design-microamp-hours: battery design capacity - charge-full-design-microamp-hours: battery design capacity
- trickle-charge-current-microamp: current for trickle-charge phase
- precharge-current-microamp: current for pre-charge phase - precharge-current-microamp: current for pre-charge phase
- precharge-upper-limit-microvolt: limit when to change to constant charging
- charge-term-current-microamp: current for charge termination phase - charge-term-current-microamp: current for charge termination phase
- constant-charge-current-max-microamp: maximum constant input current - constant-charge-current-max-microamp: maximum constant input current
- constant-charge-voltage-max-microvolt: maximum constant input voltage - constant-charge-voltage-max-microvolt: maximum constant input voltage
......
TI BQ27XXX fuel gauge family
Required properties:
- compatible: contains one of the following:
* "ti,bq27200" - BQ27200
* "ti,bq27210" - BQ27210
* "ti,bq27500" - deprecated, use revision specific property below
* "ti,bq27510" - deprecated, use revision specific property below
* "ti,bq27520" - deprecated, use revision specific property below
* "ti,bq27500-1" - BQ27500/1
* "ti,bq27510g1" - BQ27510-g1
* "ti,bq27510g2" - BQ27510-g2
* "ti,bq27510g3" - BQ27510-g3
* "ti,bq27520g1" - BQ27520-g1
* "ti,bq27520g2" - BQ27520-g2
* "ti,bq27520g3" - BQ27520-g3
* "ti,bq27520g4" - BQ27520-g4
* "ti,bq27521" - BQ27521
* "ti,bq27530" - BQ27530
* "ti,bq27531" - BQ27531
* "ti,bq27541" - BQ27541
* "ti,bq27542" - BQ27542
* "ti,bq27546" - BQ27546
* "ti,bq27742" - BQ27742
* "ti,bq27545" - BQ27545
* "ti,bq27411" - BQ27411
* "ti,bq27421" - BQ27421
* "ti,bq27425" - BQ27425
* "ti,bq27426" - BQ27426
* "ti,bq27441" - BQ27441
* "ti,bq27621" - BQ27621
- reg: integer, I2C address of the fuel gauge.
Optional properties:
- monitored-battery: phandle of battery characteristics node
The fuel gauge uses the following battery properties:
+ energy-full-design-microwatt-hours
+ charge-full-design-microamp-hours
+ voltage-min-design-microvolt
Both or neither of the *-full-design-*-hours properties must be set.
See Documentation/devicetree/bindings/power/supply/battery.txt
Example:
bat: battery {
compatible = "simple-battery";
voltage-min-design-microvolt = <3200000>;
energy-full-design-microwatt-hours = <5290000>;
charge-full-design-microamp-hours = <1430000>;
};
bq27510g3: fuel-gauge@55 {
compatible = "ti,bq27510g3";
reg = <0x55>;
monitored-battery = <&bat>;
};
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2020 Texas Instruments Incorporated
%YAML 1.2
---
$id: "http://devicetree.org/schemas/power/supply/bq27xxx.yaml#"
$schema: "http://devicetree.org/meta-schemas/core.yaml#"
title: TI BQ27XXX fuel gauge family
maintainers:
- Pali Rohár <pali@kernel.org>
- Andrew F. Davis <afd@ti.com>
- Sebastian Reichel <sre@kernel.org>
description: |
Support various Texas Instruments fuel gauge devices that share similar
register maps and power supply properties
allOf:
- $ref: power-supply.yaml#
properties:
compatible:
enum:
- ti,bq27200
- ti,bq27210
- ti,bq27500 # deprecated, use revision specific property below
- ti,bq27510 # deprecated, use revision specific property below
- ti,bq27520 # deprecated, use revision specific property below
- ti,bq27500-1
- ti,bq27510g1
- ti,bq27510g2
- ti,bq27510g3
- ti,bq27520g1
- ti,bq27520g2
- ti,bq27520g3
- ti,bq27520g4
- ti,bq27521
- ti,bq27530
- ti,bq27531
- ti,bq27541
- ti,bq27542
- ti,bq27546
- ti,bq27742
- ti,bq27545
- ti,bq27411
- ti,bq27421
- ti,bq27425
- ti,bq27426
- ti,bq27441
- ti,bq27621
reg:
maxItems: 1
description: integer, I2C address of the fuel gauge.
monitored-battery:
description: |
phandle of battery characteristics node.
The fuel gauge uses the following battery properties:
- energy-full-design-microwatt-hours
- charge-full-design-microamp-hours
- voltage-min-design-microvolt
Both or neither of the *-full-design-*-hours properties must be set.
See Documentation/devicetree/bindings/power/supply/battery.txt
power-supplies: true
required:
- compatible
- reg
additionalProperties: false
examples:
- |
i2c0 {
#address-cells = <1>;
#size-cells = <0>;
bat: battery {
compatible = "simple-battery";
voltage-min-design-microvolt = <3200000>;
energy-full-design-microwatt-hours = <5290000>;
charge-full-design-microamp-hours = <1430000>;
};
bq27510g3: fuel-gauge@55 {
compatible = "ti,bq27510g3";
reg = <0x55>;
monitored-battery = <&bat>;
};
};
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/power/supply/cw2015_battery.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Battery driver for CW2015 shuntless fuel gauge by CellWise.
maintainers:
- Tobias Schramm <t.schramm@manjaro.org>
description: |
The driver can utilize information from a simple-battery linked via a
phandle in monitored-battery. If specified the driver uses the
charge-full-design-microamp-hours property of the battery.
properties:
compatible:
const: cellwise,cw2015
reg:
maxItems: 1
cellwise,battery-profile:
description: |
This property specifies characteristics of the battery used. The format
of this binary blob is kept secret by CellWise. The only way to obtain
it is to mail two batteries to a test facility of CellWise and receive
back a test report with the binary blob.
allOf:
- $ref: /schemas/types.yaml#definitions/uint8-array
items:
- minItems: 64
maxItems: 64
cellwise,monitor-interval-ms:
description:
Specifies the interval in milliseconds gauge values are polled at
minimum: 250
power-supplies:
description:
Specifies supplies used for charging the battery connected to this gauge
allOf:
- $ref: /schemas/types.yaml#/definitions/phandle-array
- minItems: 1
maxItems: 8 # Should be enough
monitored-battery:
description:
Specifies the phandle of a simple-battery connected to this gauge
$ref: /schemas/types.yaml#/definitions/phandle
required:
- compatible
- reg
examples:
- |
i2c {
#address-cells = <1>;
#size-cells = <0>;
cw2015@62 {
compatible = "cellwise,cw201x";
reg = <0x62>;
cellwise,battery-profile = /bits/ 8 <
0x17 0x67 0x80 0x73 0x6E 0x6C 0x6B 0x63
0x77 0x51 0x5C 0x58 0x50 0x4C 0x48 0x36
0x15 0x0C 0x0C 0x19 0x5B 0x7D 0x6F 0x69
0x69 0x5B 0x0C 0x29 0x20 0x40 0x52 0x59
0x57 0x56 0x54 0x4F 0x3B 0x1F 0x7F 0x17
0x06 0x1A 0x30 0x5A 0x85 0x93 0x96 0x2D
0x48 0x77 0x9C 0xB3 0x80 0x52 0x94 0xCB
0x2F 0x00 0x64 0xA5 0xB5 0x11 0xF0 0x11
>;
cellwise,monitor-interval-ms = <5000>;
monitored-battery = <&bat>;
power-supplies = <&mains_charger>, <&usb_charger>;
};
};
# SPDX-License-Identifier: GPL-2.0
%YAML 1.2
---
$id: "http://devicetree.org/schemas/power/supply/power-supply.yaml#"
$schema: "http://devicetree.org/meta-schemas/core.yaml#"
title: Power Supply Core Support
maintainers:
- Sebastian Reichel <sre@kernel.org>
properties:
power-supplies:
$ref: /schemas/types.yaml#/definitions/phandle-array
description:
This property is added to a supply in order to list the devices which
supply it power, referenced by their phandles.
examples:
- |
power {
#address-cells = <1>;
#size-cells = <0>;
usb_charger:charger@e {
compatible = "some,usb-charger";
reg = <0xe>;
};
ac_charger:charger@c {
compatible = "some,ac-charger";
reg = <0xc>;
};
battery:battery@b {
compatible = "some,battery";
reg = <0xb>;
power-supplies = <&usb_charger>, <&ac_charger>;
};
};
Power Supply Core Support This binding has been converted to yaml please see power-supply.yaml in this
directory.
Optional Properties:
- power-supplies : This property is added to a supply in order to list the
devices which supply it power, referenced by their phandles.
Example:
usb-charger: power@e {
compatible = "some,usb-charger";
...
};
ac-charger: power@c {
compatible = "some,ac-charger";
...
};
battery@b {
compatible = "some,battery";
...
power-supplies = <&usb-charger>, <&ac-charger>;
};
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/power/supply/rohm,bd99954.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: ROHM BD99954 Battery charger
maintainers:
- Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
- Markus Laine <markus.laine@fi.rohmeurope.com>
- Mikko Mutanen <mikko.mutanen@fi.rohmeurope.com>
description: |
The ROHM BD99954 is a Battery Management LSI for 1-4 cell Lithium-Ion
secondary battery intended to be used in space-constraint equipment such
as Low profile Notebook PC, Tablets and other applications. BD99954
provides a Dual-source Battery Charger, two port BC1.2 detection and a
Battery Monitor.
properties:
compatible:
const: rohm,bd99954
#
# The battery charging profile of BD99954.
#
# Curve (1) represents charging current.
# Curve (2) represents battery voltage.
#
# The BD99954 data sheet divides charging to three phases.
# a) Trickle-charge with constant current (8).
# b) pre-charge with constant current (6)
# c) fast-charge with:
# First a constant current (5) phase (CC)
# Then constant voltage (CV) phase (after the battery voltage has reached
# target level - until charging current has dropped to termination
# level (7)
#
# V ^ ^ I
# . .
# . .
# (4)- -.- - - - - - - - - - - - - - +++++++++++++++++++++++++++.
# . / .
# . ++++++/++ - - - - - - - - - - - - -.- - (5)
# . + / + .
# . + - -- .
# . + - + .
# . +.- -: .
# . .+ +` .
# . .- + | `/ .
# . .." + .: .
# . -" + -- .
# . (2) ..." + | :- .
# . ..."" + -: .
# (3)- -.-.""- - - - -+++++++++ - - - - - - -.:- - - - - - - - - .- - (6)
# . + `:. .
# . + | -: .
# . + -: .
# . + .. .
# . (1) + | "+++- - - -.- - (7)
# -++++++++++++++- - - - - - - - - - - - - - - - - + - - - .- - (8)
# . + -
# -------------------------------------------------+++++++++-->
# | | | CC | CV |
# | --trickle-- | -pre- | ---------fast----------- |
#
# The charger uses the following battery properties
# - trickle-charge-current-microamp:
# Current used at trickle-charge phase (8 in above chart)
# minimum: 64000
# maximum: 1024000
# multipleOf: 64000
# - precharge-current-microamp:
# Current used at pre-charge phase (6 in above chart)
# minimum: 64000
# maximum: 1024000
# multipleOf: 64000
# - constant-charge-current-max-microamp
# Current used at fast charge constant current phase (5 in above chart)
# minimum: 64000
# maximum: 1024000
# multipleOf: 64000
# - constant-charge-voltage-max-microvolt
# The constant voltage used in fast charging phase (4 in above chart)
# minimum: 2560000
# maximum: 19200000
# multipleOf: 16000
# - precharge-upper-limit-microvolt
# charging mode is changed from trickle charging to pre-charging
# when battery voltage exceeds this limit voltage (3 in above chart)
# minimum: 2048000
# maximum: 19200000
# multipleOf: 64000
# - re-charge-voltage-microvolt
# minimum: 2560000
# maximum: 19200000
# multipleOf: 16000
# re-charging is automatically started when battry has been discharging
# to the point where the battery voltage drops below this limit
# - over-voltage-threshold-microvolt
# battery is expected to be faulty if battery voltage exceeds this limit.
# Charger will then enter to a "battery faulty" -state
# minimum: 2560000
# maximum: 19200000
# multipleOf: 16000
# - charge-term-current-microamp
# minimum: 0
# maximum: 1024000
# multipleOf: 64000
# a charge cycle terminates when the battery voltage is above recharge
# threshold, and the current is below this setting (7 in above chart)
# See also Documentation/devicetree/bindings/power/supply/battery.txt
monitored-battery:
description:
phandle of battery characteristics devicetree node
rohm,vsys-regulation-microvolt:
description: system specific lower limit for system voltage.
minimum: 2560000
maximum: 19200000
multipleOf: 64000
rohm,vbus-input-current-limit-microamp:
description: system specific VBUS input current limit (in microamps).
minimum: 32000
maximum: 16352000
multipleOf: 32000
rohm,vcc-input-current-limit-microamp:
description: system specific VCC/VACP input current limit (in microamps).
minimum: 32000
maximum: 16352000
multipleOf: 32000
required:
- compatible
examples:
- |
i2c {
#address-cells = <1>;
#size-cells = <0>;
charger@9 {
compatible = "rohm,bd99954";
monitored-battery = <&battery>;
reg = <0x9>;
interrupt-parent = <&gpio1>;
interrupts = <29 8>;
rohm,vsys-regulation-microvolt = <8960000>;
rohm,vbus-input-current-limit-microamp = <1472000>;
rohm,vcc-input-current-limit-microamp = <1472000>;
};
};
# SPDX-License-Identifier: GPL-2.0
%YAML 1.2
---
$id: http://devicetree.org/schemas/power/supply/sbs,sbs-battery.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: SBS compliant battery
maintainers:
- Sebastian Reichel <sre@kernel.org>
description: |
Battery compatible with the smart battery system specifications
properties:
compatible:
oneOf:
- items:
- enum:
- ti,bq20z65
- ti,bq20z75
- enum:
- sbs,sbs-battery
- items:
- const: sbs,sbs-battery
reg:
maxItems: 1
sbs,i2c-retry-count:
description:
The number of times to retry I2C transactions on I2C IO failure.
default: 0
allOf:
- $ref: /schemas/types.yaml#/definitions/uint32
sbs,poll-retry-count:
description:
The number of times to try looking for new status after an external
change notification.
default: 0
allOf:
- $ref: /schemas/types.yaml#/definitions/uint32
sbs,battery-detect-gpios:
description:
GPIO which signals battery detection. If this is not supplied, the bus
needs to be polled to detect the battery.
maxItems: 1
sbs,disable-charger-broadcasts:
description:
SBS batteries by default send broadcast messages to SBS compliant chargers to
configure max. charge current/voltage. If your hardware does not have an SBS
compliant charger it should be disabled via this property to avoid blocking
the bus. Also some SBS battery fuel gauges are known to have a buggy multi-
master implementation.
type: boolean
required:
- compatible
- reg
additionalProperties: false
examples:
- |
#include <dt-bindings/gpio/gpio.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
battery@b {
compatible = "ti,bq20z75", "sbs,sbs-battery";
reg = <0xb>;
sbs,i2c-retry-count = <2>;
sbs,poll-retry-count = <10>;
sbs,battery-detect-gpios = <&gpio 122 GPIO_ACTIVE_HIGH>;
sbs,disable-charger-broadcasts;
};
};
SBS sbs-battery
~~~~~~~~~~
Required properties :
- compatible: "<vendor>,<part-number>", "sbs,sbs-battery" as fallback. The
part number compatible string might be used in order to take care of
vendor specific registers.
Known <vendor>,<part-number>:
ti,bq20z75
Optional properties :
- sbs,i2c-retry-count : The number of times to retry i2c transactions on i2c
IO failure.
- sbs,poll-retry-count : The number of times to try looking for new status
after an external change notification.
- sbs,battery-detect-gpios : The gpio which signals battery detection and
a flag specifying its polarity.
Example:
battery@b {
compatible = "ti,bq20z75", "sbs,sbs-battery";
reg = <0xb>;
sbs,i2c-retry-count = <2>;
sbs,poll-retry-count = <10>;
sbs,battery-detect-gpios = <&gpio-controller 122 1>;
}
...@@ -187,6 +187,8 @@ patternProperties: ...@@ -187,6 +187,8 @@ patternProperties:
description: Cadence Design Systems Inc. description: Cadence Design Systems Inc.
"^cdtech,.*": "^cdtech,.*":
description: CDTech(H.K.) Electronics Limited description: CDTech(H.K.) Electronics Limited
"^cellwise,.*":
description: CellWise Microelectronics Co., Ltd
"^ceva,.*": "^ceva,.*":
description: Ceva, Inc. description: Ceva, Inc.
"^checkpoint,.*": "^checkpoint,.*":
......
...@@ -2190,6 +2190,7 @@ L: linux-oxnas@groups.io (moderated for non-subscribers) ...@@ -2190,6 +2190,7 @@ L: linux-oxnas@groups.io (moderated for non-subscribers)
S: Maintained S: Maintained
F: arch/arm/boot/dts/ox8*.dts* F: arch/arm/boot/dts/ox8*.dts*
F: arch/arm/mach-oxnas/ F: arch/arm/mach-oxnas/
F: drivers/power/reset/oxnas-restart.c
N: oxnas N: oxnas
ARM/PALM TREO SUPPORT ARM/PALM TREO SUPPORT
...@@ -3978,6 +3979,12 @@ F: arch/powerpc/include/uapi/asm/spu*.h ...@@ -3978,6 +3979,12 @@ F: arch/powerpc/include/uapi/asm/spu*.h
F: arch/powerpc/oprofile/*cell* F: arch/powerpc/oprofile/*cell*
F: arch/powerpc/platforms/cell/ F: arch/powerpc/platforms/cell/
CELLWISE CW2015 BATTERY DRIVER
M: Tobias Schrammm <t.schramm@manjaro.org>
S: Maintained
F: Documentation/devicetree/bindings/power/supply/cw2015_battery.yaml
F: drivers/power/supply/cw2015_battery.c
CEPH COMMON CODE (LIBCEPH) CEPH COMMON CODE (LIBCEPH)
M: Ilya Dryomov <idryomov@gmail.com> M: Ilya Dryomov <idryomov@gmail.com>
M: Jeff Layton <jlayton@kernel.org> M: Jeff Layton <jlayton@kernel.org>
......
...@@ -52,7 +52,7 @@ static const char * const lid_wake_mode_names[] = { ...@@ -52,7 +52,7 @@ static const char * const lid_wake_mode_names[] = {
static void battery_status_changed(void) static void battery_status_changed(void)
{ {
struct power_supply *psy = power_supply_get_by_name("olpc-battery"); struct power_supply *psy = power_supply_get_by_name("olpc_battery");
if (psy) { if (psy) {
power_supply_changed(psy); power_supply_changed(psy);
...@@ -62,7 +62,7 @@ static void battery_status_changed(void) ...@@ -62,7 +62,7 @@ static void battery_status_changed(void)
static void ac_status_changed(void) static void ac_status_changed(void)
{ {
struct power_supply *psy = power_supply_get_by_name("olpc-ac"); struct power_supply *psy = power_supply_get_by_name("olpc_ac");
if (psy) { if (psy) {
power_supply_changed(psy); power_supply_changed(psy);
......
...@@ -75,7 +75,7 @@ static struct kobj_attribute lid_wake_on_close_attr = ...@@ -75,7 +75,7 @@ static struct kobj_attribute lid_wake_on_close_attr =
static void battery_status_changed(void) static void battery_status_changed(void)
{ {
struct power_supply *psy = power_supply_get_by_name("olpc-battery"); struct power_supply *psy = power_supply_get_by_name("olpc_battery");
if (psy) { if (psy) {
power_supply_changed(psy); power_supply_changed(psy);
...@@ -85,7 +85,7 @@ static void battery_status_changed(void) ...@@ -85,7 +85,7 @@ static void battery_status_changed(void)
static void ac_status_changed(void) static void ac_status_changed(void)
{ {
struct power_supply *psy = power_supply_get_by_name("olpc-ac"); struct power_supply *psy = power_supply_get_by_name("olpc_ac");
if (psy) { if (psy) {
power_supply_changed(psy); power_supply_changed(psy);
......
...@@ -410,7 +410,7 @@ static void olpc_xo175_ec_complete(void *arg) ...@@ -410,7 +410,7 @@ static void olpc_xo175_ec_complete(void *arg)
dev_dbg(dev, "got event %.2x\n", byte); dev_dbg(dev, "got event %.2x\n", byte);
switch (byte) { switch (byte) {
case EVENT_AC_CHANGE: case EVENT_AC_CHANGE:
psy = power_supply_get_by_name("olpc-ac"); psy = power_supply_get_by_name("olpc_ac");
if (psy) { if (psy) {
power_supply_changed(psy); power_supply_changed(psy);
power_supply_put(psy); power_supply_put(psy);
...@@ -420,7 +420,7 @@ static void olpc_xo175_ec_complete(void *arg) ...@@ -420,7 +420,7 @@ static void olpc_xo175_ec_complete(void *arg)
case EVENT_BATTERY_CRITICAL: case EVENT_BATTERY_CRITICAL:
case EVENT_BATTERY_SOC_CHANGE: case EVENT_BATTERY_SOC_CHANGE:
case EVENT_BATTERY_ERROR: case EVENT_BATTERY_ERROR:
psy = power_supply_get_by_name("olpc-battery"); psy = power_supply_get_by_name("olpc_battery");
if (psy) { if (psy) {
power_supply_changed(psy); power_supply_changed(psy);
power_supply_put(psy); power_supply_put(psy);
......
...@@ -123,6 +123,13 @@ config POWER_RESET_OCELOT_RESET ...@@ -123,6 +123,13 @@ config POWER_RESET_OCELOT_RESET
help help
This driver supports restart for Microsemi Ocelot SoC. This driver supports restart for Microsemi Ocelot SoC.
config POWER_RESET_OXNAS
bool "OXNAS SoC restart driver"
depends on ARCH_OXNAS
default MACH_OX820
help
Restart support for OXNAS/PLXTECH OX820 SoC.
config POWER_RESET_PIIX4_POWEROFF config POWER_RESET_PIIX4_POWEROFF
tristate "Intel PIIX4 power-off driver" tristate "Intel PIIX4 power-off driver"
depends on PCI depends on PCI
......
...@@ -12,6 +12,7 @@ obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o ...@@ -12,6 +12,7 @@ obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o
obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o
obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o
obj-$(CONFIG_POWER_RESET_MT6323) += mt6323-poweroff.o obj-$(CONFIG_POWER_RESET_MT6323) += mt6323-poweroff.o
obj-$(CONFIG_POWER_RESET_OXNAS) += oxnas-restart.o
obj-$(CONFIG_POWER_RESET_QCOM_PON) += qcom-pon.o obj-$(CONFIG_POWER_RESET_QCOM_PON) += qcom-pon.o
obj-$(CONFIG_POWER_RESET_OCELOT_RESET) += ocelot-reset.o obj-$(CONFIG_POWER_RESET_OCELOT_RESET) += ocelot-reset.o
obj-$(CONFIG_POWER_RESET_PIIX4_POWEROFF) += piix4-poweroff.o obj-$(CONFIG_POWER_RESET_PIIX4_POWEROFF) += piix4-poweroff.o
......
...@@ -54,7 +54,7 @@ static int gpio_poweroff_probe(struct platform_device *pdev) ...@@ -54,7 +54,7 @@ static int gpio_poweroff_probe(struct platform_device *pdev)
/* If a pm_power_off function has already been added, leave it alone */ /* If a pm_power_off function has already been added, leave it alone */
if (pm_power_off != NULL) { if (pm_power_off != NULL) {
dev_err(&pdev->dev, dev_err(&pdev->dev,
"%s: pm_power_off function already registered", "%s: pm_power_off function already registered\n",
__func__); __func__);
return -EBUSY; return -EBUSY;
} }
......
...@@ -94,7 +94,6 @@ static enum hrtimer_restart ltc2952_poweroff_timer_wde(struct hrtimer *timer) ...@@ -94,7 +94,6 @@ static enum hrtimer_restart ltc2952_poweroff_timer_wde(struct hrtimer *timer)
{ {
ktime_t now; ktime_t now;
int state; int state;
unsigned long overruns;
struct ltc2952_poweroff *data = to_ltc2952(timer, timer_wde); struct ltc2952_poweroff *data = to_ltc2952(timer, timer_wde);
if (data->kernel_panic) if (data->kernel_panic)
...@@ -104,7 +103,7 @@ static enum hrtimer_restart ltc2952_poweroff_timer_wde(struct hrtimer *timer) ...@@ -104,7 +103,7 @@ static enum hrtimer_restart ltc2952_poweroff_timer_wde(struct hrtimer *timer)
gpiod_set_value(data->gpio_watchdog, !state); gpiod_set_value(data->gpio_watchdog, !state);
now = hrtimer_cb_get_time(timer); now = hrtimer_cb_get_time(timer);
overruns = hrtimer_forward(timer, now, data->wde_interval); hrtimer_forward(timer, now, data->wde_interval);
return HRTIMER_RESTART; return HRTIMER_RESTART;
} }
......
// SPDX-License-Identifier: (GPL-2.0)
/*
* oxnas SoC reset driver
* based on:
* Microsemi MIPS SoC reset driver
* and ox820_assert_system_reset() written by Ma Hajun <mahaijuns@gmail.com>
*
* Copyright (c) 2013 Ma Hajun <mahaijuns@gmail.com>
* Copyright (c) 2017 Microsemi Corporation
* Copyright (c) 2020 Daniel Golle <daniel@makrotopia.org>
*/
#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>
/* bit numbers of reset control register */
#define OX820_SYS_CTRL_RST_SCU 0
#define OX820_SYS_CTRL_RST_COPRO 1
#define OX820_SYS_CTRL_RST_ARM0 2
#define OX820_SYS_CTRL_RST_ARM1 3
#define OX820_SYS_CTRL_RST_USBHS 4
#define OX820_SYS_CTRL_RST_USBHSPHYA 5
#define OX820_SYS_CTRL_RST_MACA 6
#define OX820_SYS_CTRL_RST_MAC OX820_SYS_CTRL_RST_MACA
#define OX820_SYS_CTRL_RST_PCIEA 7
#define OX820_SYS_CTRL_RST_SGDMA 8
#define OX820_SYS_CTRL_RST_CIPHER 9
#define OX820_SYS_CTRL_RST_DDR 10
#define OX820_SYS_CTRL_RST_SATA 11
#define OX820_SYS_CTRL_RST_SATA_LINK 12
#define OX820_SYS_CTRL_RST_SATA_PHY 13
#define OX820_SYS_CTRL_RST_PCIEPHY 14
#define OX820_SYS_CTRL_RST_STATIC 15
#define OX820_SYS_CTRL_RST_GPIO 16
#define OX820_SYS_CTRL_RST_UART1 17
#define OX820_SYS_CTRL_RST_UART2 18
#define OX820_SYS_CTRL_RST_MISC 19
#define OX820_SYS_CTRL_RST_I2S 20
#define OX820_SYS_CTRL_RST_SD 21
#define OX820_SYS_CTRL_RST_MACB 22
#define OX820_SYS_CTRL_RST_PCIEB 23
#define OX820_SYS_CTRL_RST_VIDEO 24
#define OX820_SYS_CTRL_RST_DDR_PHY 25
#define OX820_SYS_CTRL_RST_USBHSPHYB 26
#define OX820_SYS_CTRL_RST_USBDEV 27
#define OX820_SYS_CTRL_RST_ARMDBG 29
#define OX820_SYS_CTRL_RST_PLLA 30
#define OX820_SYS_CTRL_RST_PLLB 31
/* bit numbers of clock control register */
#define OX820_SYS_CTRL_CLK_COPRO 0
#define OX820_SYS_CTRL_CLK_DMA 1
#define OX820_SYS_CTRL_CLK_CIPHER 2
#define OX820_SYS_CTRL_CLK_SD 3
#define OX820_SYS_CTRL_CLK_SATA 4
#define OX820_SYS_CTRL_CLK_I2S 5
#define OX820_SYS_CTRL_CLK_USBHS 6
#define OX820_SYS_CTRL_CLK_MACA 7
#define OX820_SYS_CTRL_CLK_MAC OX820_SYS_CTRL_CLK_MACA
#define OX820_SYS_CTRL_CLK_PCIEA 8
#define OX820_SYS_CTRL_CLK_STATIC 9
#define OX820_SYS_CTRL_CLK_MACB 10
#define OX820_SYS_CTRL_CLK_PCIEB 11
#define OX820_SYS_CTRL_CLK_REF600 12
#define OX820_SYS_CTRL_CLK_USBDEV 13
#define OX820_SYS_CTRL_CLK_DDR 14
#define OX820_SYS_CTRL_CLK_DDRPHY 15
#define OX820_SYS_CTRL_CLK_DDRCK 16
/* Regmap offsets */
#define OX820_CLK_SET_REGOFFSET 0x2c
#define OX820_CLK_CLR_REGOFFSET 0x30
#define OX820_RST_SET_REGOFFSET 0x34
#define OX820_RST_CLR_REGOFFSET 0x38
#define OX820_SECONDARY_SEL_REGOFFSET 0x14
#define OX820_TERTIARY_SEL_REGOFFSET 0x8c
#define OX820_QUATERNARY_SEL_REGOFFSET 0x94
#define OX820_DEBUG_SEL_REGOFFSET 0x9c
#define OX820_ALTERNATIVE_SEL_REGOFFSET 0xa4
#define OX820_PULLUP_SEL_REGOFFSET 0xac
#define OX820_SEC_SECONDARY_SEL_REGOFFSET 0x100014
#define OX820_SEC_TERTIARY_SEL_REGOFFSET 0x10008c
#define OX820_SEC_QUATERNARY_SEL_REGOFFSET 0x100094
#define OX820_SEC_DEBUG_SEL_REGOFFSET 0x10009c
#define OX820_SEC_ALTERNATIVE_SEL_REGOFFSET 0x1000a4
#define OX820_SEC_PULLUP_SEL_REGOFFSET 0x1000ac
struct oxnas_restart_context {
struct regmap *sys_ctrl;
struct notifier_block restart_handler;
};
static int ox820_restart_handle(struct notifier_block *this,
unsigned long mode, void *cmd)
{
struct oxnas_restart_context *ctx = container_of(this, struct
oxnas_restart_context,
restart_handler);
u32 value;
/*
* Assert reset to cores as per power on defaults
* Don't touch the DDR interface as things will come to an impromptu
* stop NB Possibly should be asserting reset for PLLB, but there are
* timing concerns here according to the docs
*/
value = BIT(OX820_SYS_CTRL_RST_COPRO) |
BIT(OX820_SYS_CTRL_RST_USBHS) |
BIT(OX820_SYS_CTRL_RST_USBHSPHYA) |
BIT(OX820_SYS_CTRL_RST_MACA) |
BIT(OX820_SYS_CTRL_RST_PCIEA) |
BIT(OX820_SYS_CTRL_RST_SGDMA) |
BIT(OX820_SYS_CTRL_RST_CIPHER) |
BIT(OX820_SYS_CTRL_RST_SATA) |
BIT(OX820_SYS_CTRL_RST_SATA_LINK) |
BIT(OX820_SYS_CTRL_RST_SATA_PHY) |
BIT(OX820_SYS_CTRL_RST_PCIEPHY) |
BIT(OX820_SYS_CTRL_RST_STATIC) |
BIT(OX820_SYS_CTRL_RST_UART1) |
BIT(OX820_SYS_CTRL_RST_UART2) |
BIT(OX820_SYS_CTRL_RST_MISC) |
BIT(OX820_SYS_CTRL_RST_I2S) |
BIT(OX820_SYS_CTRL_RST_SD) |
BIT(OX820_SYS_CTRL_RST_MACB) |
BIT(OX820_SYS_CTRL_RST_PCIEB) |
BIT(OX820_SYS_CTRL_RST_VIDEO) |
BIT(OX820_SYS_CTRL_RST_USBHSPHYB) |
BIT(OX820_SYS_CTRL_RST_USBDEV);
regmap_write(ctx->sys_ctrl, OX820_RST_SET_REGOFFSET, value);
/* Release reset to cores as per power on defaults */
regmap_write(ctx->sys_ctrl, OX820_RST_CLR_REGOFFSET,
BIT(OX820_SYS_CTRL_RST_GPIO));
/*
* Disable clocks to cores as per power-on defaults - must leave DDR
* related clocks enabled otherwise we'll stop rather abruptly.
*/
value = BIT(OX820_SYS_CTRL_CLK_COPRO) |
BIT(OX820_SYS_CTRL_CLK_DMA) |
BIT(OX820_SYS_CTRL_CLK_CIPHER) |
BIT(OX820_SYS_CTRL_CLK_SD) |
BIT(OX820_SYS_CTRL_CLK_SATA) |
BIT(OX820_SYS_CTRL_CLK_I2S) |
BIT(OX820_SYS_CTRL_CLK_USBHS) |
BIT(OX820_SYS_CTRL_CLK_MAC) |
BIT(OX820_SYS_CTRL_CLK_PCIEA) |
BIT(OX820_SYS_CTRL_CLK_STATIC) |
BIT(OX820_SYS_CTRL_CLK_MACB) |
BIT(OX820_SYS_CTRL_CLK_PCIEB) |
BIT(OX820_SYS_CTRL_CLK_REF600) |
BIT(OX820_SYS_CTRL_CLK_USBDEV);
regmap_write(ctx->sys_ctrl, OX820_CLK_CLR_REGOFFSET, value);
/* Enable clocks to cores as per power-on defaults */
/* Set sys-control pin mux'ing as per power-on defaults */
regmap_write(ctx->sys_ctrl, OX820_SECONDARY_SEL_REGOFFSET, 0);
regmap_write(ctx->sys_ctrl, OX820_TERTIARY_SEL_REGOFFSET, 0);
regmap_write(ctx->sys_ctrl, OX820_QUATERNARY_SEL_REGOFFSET, 0);
regmap_write(ctx->sys_ctrl, OX820_DEBUG_SEL_REGOFFSET, 0);
regmap_write(ctx->sys_ctrl, OX820_ALTERNATIVE_SEL_REGOFFSET, 0);
regmap_write(ctx->sys_ctrl, OX820_PULLUP_SEL_REGOFFSET, 0);
regmap_write(ctx->sys_ctrl, OX820_SEC_SECONDARY_SEL_REGOFFSET, 0);
regmap_write(ctx->sys_ctrl, OX820_SEC_TERTIARY_SEL_REGOFFSET, 0);
regmap_write(ctx->sys_ctrl, OX820_SEC_QUATERNARY_SEL_REGOFFSET, 0);
regmap_write(ctx->sys_ctrl, OX820_SEC_DEBUG_SEL_REGOFFSET, 0);
regmap_write(ctx->sys_ctrl, OX820_SEC_ALTERNATIVE_SEL_REGOFFSET, 0);
regmap_write(ctx->sys_ctrl, OX820_SEC_PULLUP_SEL_REGOFFSET, 0);
/*
* No need to save any state, as the ROM loader can determine whether
* reset is due to power cycling or programatic action, just hit the
* (self-clearing) CPU reset bit of the block reset register
*/
value =
BIT(OX820_SYS_CTRL_RST_SCU) |
BIT(OX820_SYS_CTRL_RST_ARM0) |
BIT(OX820_SYS_CTRL_RST_ARM1);
regmap_write(ctx->sys_ctrl, OX820_RST_SET_REGOFFSET, value);
pr_emerg("Unable to restart system\n");
return NOTIFY_DONE;
}
static int ox820_restart_probe(struct platform_device *pdev)
{
struct oxnas_restart_context *ctx;
struct regmap *sys_ctrl;
struct device *dev = &pdev->dev;
int err = 0;
sys_ctrl = syscon_node_to_regmap(pdev->dev.of_node);
if (IS_ERR(sys_ctrl))
return PTR_ERR(sys_ctrl);
ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
ctx->sys_ctrl = sys_ctrl;
ctx->restart_handler.notifier_call = ox820_restart_handle;
ctx->restart_handler.priority = 192;
err = register_restart_handler(&ctx->restart_handler);
if (err)
dev_err(dev, "can't register restart notifier (err=%d)\n", err);
return err;
}
static const struct of_device_id ox820_restart_of_match[] = {
{ .compatible = "oxsemi,ox820-sys-ctrl" },
{}
};
static struct platform_driver ox820_restart_driver = {
.probe = ox820_restart_probe,
.driver = {
.name = "ox820-chip-reset",
.of_match_table = ox820_restart_of_match,
},
};
builtin_platform_driver(ox820_restart_driver);
...@@ -34,7 +34,8 @@ static int pm8916_reboot_mode_write(struct reboot_mode_driver *reboot, ...@@ -34,7 +34,8 @@ static int pm8916_reboot_mode_write(struct reboot_mode_driver *reboot,
ret = regmap_update_bits(pon->regmap, ret = regmap_update_bits(pon->regmap,
pon->baseaddr + PON_SOFT_RB_SPARE, pon->baseaddr + PON_SOFT_RB_SPARE,
0xfc, magic << pon->reason_shift); GENMASK(7, pon->reason_shift),
magic << pon->reason_shift);
if (ret < 0) if (ret < 0)
dev_err(pon->dev, "update reboot mode bits failed\n"); dev_err(pon->dev, "update reboot mode bits failed\n");
......
...@@ -51,8 +51,11 @@ static int syscon_reboot_probe(struct platform_device *pdev) ...@@ -51,8 +51,11 @@ static int syscon_reboot_probe(struct platform_device *pdev)
return -ENOMEM; return -ENOMEM;
ctx->map = syscon_regmap_lookup_by_phandle(dev->of_node, "regmap"); ctx->map = syscon_regmap_lookup_by_phandle(dev->of_node, "regmap");
if (IS_ERR(ctx->map)) if (IS_ERR(ctx->map)) {
return PTR_ERR(ctx->map); ctx->map = syscon_node_to_regmap(dev->parent->of_node);
if (IS_ERR(ctx->map))
return PTR_ERR(ctx->map);
}
if (of_property_read_u32(pdev->dev.of_node, "offset", &ctx->offset)) if (of_property_read_u32(pdev->dev.of_node, "offset", &ctx->offset))
return -EINVAL; return -EINVAL;
......
...@@ -919,16 +919,12 @@ static int pm860x_battery_probe(struct platform_device *pdev) ...@@ -919,16 +919,12 @@ static int pm860x_battery_probe(struct platform_device *pdev)
return -ENOMEM; return -ENOMEM;
info->irq_cc = platform_get_irq(pdev, 0); info->irq_cc = platform_get_irq(pdev, 0);
if (info->irq_cc <= 0) { if (info->irq_cc <= 0)
dev_err(&pdev->dev, "No IRQ resource!\n");
return -EINVAL; return -EINVAL;
}
info->irq_batt = platform_get_irq(pdev, 1); info->irq_batt = platform_get_irq(pdev, 1);
if (info->irq_batt <= 0) { if (info->irq_batt <= 0)
dev_err(&pdev->dev, "No IRQ resource!\n");
return -EINVAL; return -EINVAL;
}
info->chip = chip; info->chip = chip;
info->i2c = info->i2c =
......
...@@ -116,6 +116,17 @@ config BATTERY_CPCAP ...@@ -116,6 +116,17 @@ config BATTERY_CPCAP
Say Y here to enable support for battery on Motorola Say Y here to enable support for battery on Motorola
phones and tablets such as droid 4. phones and tablets such as droid 4.
config BATTERY_CW2015
tristate "CW2015 Battery driver"
depends on I2C
select REGMAP_I2C
help
Say Y here to enable support for the cellwise cw2015
battery fuel gauge (used in the Pinebook Pro & others)
This driver can also be built as a module. If so, the module will be
called cw2015_battery.
config BATTERY_DS2760 config BATTERY_DS2760
tristate "DS2760 battery driver (HP iPAQ & others)" tristate "DS2760 battery driver (HP iPAQ & others)"
depends on W1 depends on W1
...@@ -415,7 +426,7 @@ config CHARGER_PCF50633 ...@@ -415,7 +426,7 @@ config CHARGER_PCF50633
tristate "NXP PCF50633 MBC" tristate "NXP PCF50633 MBC"
depends on MFD_PCF50633 depends on MFD_PCF50633
help help
Say Y to include support for NXP PCF50633 Main Battery Charger. Say Y to include support for NXP PCF50633 Main Battery Charger.
config BATTERY_RX51 config BATTERY_RX51
tristate "Nokia RX-51 (N900) battery driver" tristate "Nokia RX-51 (N900) battery driver"
...@@ -587,7 +598,7 @@ config CHARGER_BQ24257 ...@@ -587,7 +598,7 @@ config CHARGER_BQ24257
tristate "TI BQ24250/24251/24257 battery charger driver" tristate "TI BQ24250/24251/24257 battery charger driver"
depends on I2C depends on I2C
depends on GPIOLIB || COMPILE_TEST depends on GPIOLIB || COMPILE_TEST
depends on REGMAP_I2C select REGMAP_I2C
help help
Say Y to enable support for the TI BQ24250, BQ24251, and BQ24257 battery Say Y to enable support for the TI BQ24250, BQ24251, and BQ24257 battery
chargers. chargers.
...@@ -619,15 +630,15 @@ config CHARGER_TPS65090 ...@@ -619,15 +630,15 @@ config CHARGER_TPS65090
tristate "TPS65090 battery charger driver" tristate "TPS65090 battery charger driver"
depends on MFD_TPS65090 depends on MFD_TPS65090
help help
Say Y here to enable support for battery charging with TPS65090 Say Y here to enable support for battery charging with TPS65090
PMIC chips. PMIC chips.
config CHARGER_TPS65217 config CHARGER_TPS65217
tristate "TPS65217 battery charger driver" tristate "TPS65217 battery charger driver"
depends on MFD_TPS65217 depends on MFD_TPS65217
help help
Say Y here to enable support for battery charging with TPS65217 Say Y here to enable support for battery charging with TPS65217
PMIC chips. PMIC chips.
config BATTERY_GAUGE_LTC2941 config BATTERY_GAUGE_LTC2941
tristate "LTC2941/LTC2943 Battery Gauge Driver" tristate "LTC2941/LTC2943 Battery Gauge Driver"
...@@ -670,7 +681,6 @@ config CHARGER_RT9455 ...@@ -670,7 +681,6 @@ config CHARGER_RT9455
config CHARGER_CROS_USBPD config CHARGER_CROS_USBPD
tristate "ChromeOS EC based USBPD charger" tristate "ChromeOS EC based USBPD charger"
depends on CROS_USBPD_NOTIFY depends on CROS_USBPD_NOTIFY
default n
help help
Say Y here to enable ChromeOS EC based USBPD charger Say Y here to enable ChromeOS EC based USBPD charger
driver. This driver gets various bits of information about driver. This driver gets various bits of information about
...@@ -681,16 +691,16 @@ config CHARGER_SC2731 ...@@ -681,16 +691,16 @@ config CHARGER_SC2731
tristate "Spreadtrum SC2731 charger driver" tristate "Spreadtrum SC2731 charger driver"
depends on MFD_SC27XX_PMIC || COMPILE_TEST depends on MFD_SC27XX_PMIC || COMPILE_TEST
help help
Say Y here to enable support for battery charging with SC2731 Say Y here to enable support for battery charging with SC2731
PMIC chips. PMIC chips.
config FUEL_GAUGE_SC27XX config FUEL_GAUGE_SC27XX
tristate "Spreadtrum SC27XX fuel gauge driver" tristate "Spreadtrum SC27XX fuel gauge driver"
depends on MFD_SC27XX_PMIC || COMPILE_TEST depends on MFD_SC27XX_PMIC || COMPILE_TEST
depends on IIO depends on IIO
help help
Say Y here to enable support for fuel gauge with SC27XX Say Y here to enable support for fuel gauge with SC27XX
PMIC chips. PMIC chips.
config CHARGER_UCS1002 config CHARGER_UCS1002
tristate "Microchip UCS1002 USB Port Power Controller" tristate "Microchip UCS1002 USB Port Power Controller"
...@@ -705,11 +715,20 @@ config CHARGER_UCS1002 ...@@ -705,11 +715,20 @@ config CHARGER_UCS1002
config CHARGER_BD70528 config CHARGER_BD70528
tristate "ROHM bd70528 charger driver" tristate "ROHM bd70528 charger driver"
depends on MFD_ROHM_BD70528 depends on MFD_ROHM_BD70528
default n select LINEAR_RANGES
help
Say Y here to enable support for getting battery status
information and altering charger configurations from charger
block of the ROHM BD70528 Power Management IC.
config CHARGER_BD99954
tristate "ROHM bd99954 charger driver"
depends on I2C
select LINEAR_RANGES
help help
Say Y here to enable support for getting battery status Say Y here to enable support for getting battery and charger
information and altering charger configurations from charger information and altering charger configurations from the ROHM
block of the ROHM BD70528 Power Management IC. BD99954 charger IC.
config CHARGER_WILCO config CHARGER_WILCO
tristate "Wilco EC based charger for ChromeOS" tristate "Wilco EC based charger for ChromeOS"
......
...@@ -24,6 +24,7 @@ obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o ...@@ -24,6 +24,7 @@ obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o
obj-$(CONFIG_BATTERY_AXP20X) += axp20x_battery.o obj-$(CONFIG_BATTERY_AXP20X) += axp20x_battery.o
obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o
obj-$(CONFIG_BATTERY_CPCAP) += cpcap-battery.o obj-$(CONFIG_BATTERY_CPCAP) += cpcap-battery.o
obj-$(CONFIG_BATTERY_CW2015) += cw2015_battery.o
obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o
obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o
obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o
...@@ -92,4 +93,5 @@ obj-$(CONFIG_CHARGER_SC2731) += sc2731_charger.o ...@@ -92,4 +93,5 @@ obj-$(CONFIG_CHARGER_SC2731) += sc2731_charger.o
obj-$(CONFIG_FUEL_GAUGE_SC27XX) += sc27xx_fuel_gauge.o obj-$(CONFIG_FUEL_GAUGE_SC27XX) += sc27xx_fuel_gauge.o
obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o
obj-$(CONFIG_CHARGER_BD70528) += bd70528-charger.o obj-$(CONFIG_CHARGER_BD70528) += bd70528-charger.o
obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o
obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o
...@@ -2399,7 +2399,7 @@ static void ab8500_fg_reinit_work(struct work_struct *work) ...@@ -2399,7 +2399,7 @@ static void ab8500_fg_reinit_work(struct work_struct *work)
struct ab8500_fg *di = container_of(work, struct ab8500_fg, struct ab8500_fg *di = container_of(work, struct ab8500_fg,
fg_reinit_work.work); fg_reinit_work.work);
if (di->flags.calibrate == false) { if (!di->flags.calibrate) {
dev_dbg(di->dev, "Resetting FG state machine to init.\n"); dev_dbg(di->dev, "Resetting FG state machine to init.\n");
ab8500_fg_clear_cap_samples(di); ab8500_fg_clear_cap_samples(di);
ab8500_fg_calc_cap_discharge_voltage(di, true); ab8500_fg_calc_cap_discharge_voltage(di, true);
......
...@@ -880,10 +880,9 @@ static int axp288_charger_probe(struct platform_device *pdev) ...@@ -880,10 +880,9 @@ static int axp288_charger_probe(struct platform_device *pdev)
/* Register charger interrupts */ /* Register charger interrupts */
for (i = 0; i < CHRG_INTR_END; i++) { for (i = 0; i < CHRG_INTR_END; i++) {
pirq = platform_get_irq(info->pdev, i); pirq = platform_get_irq(info->pdev, i);
if (pirq < 0) { if (pirq < 0)
dev_err(&pdev->dev, "Failed to get IRQ: %d\n", pirq);
return pirq; return pirq;
}
info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq); info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
if (info->irq[i] < 0) { if (info->irq[i] < 0) {
dev_warn(&info->pdev->dev, dev_warn(&info->pdev->dev,
......
...@@ -717,6 +717,12 @@ static const struct dmi_system_id axp288_fuel_gauge_blacklist[] = { ...@@ -717,6 +717,12 @@ static const struct dmi_system_id axp288_fuel_gauge_blacklist[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "STK1A32SC"), DMI_MATCH(DMI_PRODUCT_NAME, "STK1A32SC"),
}, },
}, },
{
/* Meegopad T02 */
.matches = {
DMI_MATCH(DMI_PRODUCT_NAME, "MEEGOPAD T02"),
},
},
{ {
/* Meegopad T08 */ /* Meegopad T08 */
.matches = { .matches = {
......
...@@ -72,6 +72,7 @@ ...@@ -72,6 +72,7 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/power_supply.h> #include <linux/power_supply.h>
#include <linux/linear_range.h>
#define CHG_STAT_SUSPEND 0x0 #define CHG_STAT_SUSPEND 0x0
#define CHG_STAT_TRICKLE 0x1 #define CHG_STAT_TRICKLE 0x1
...@@ -335,38 +336,37 @@ static int bd70528_get_present(struct bd70528_psy *bdpsy, int *val) ...@@ -335,38 +336,37 @@ static int bd70528_get_present(struct bd70528_psy *bdpsy, int *val)
return 0; return 0;
} }
struct bd70528_linear_range { static const struct linear_range current_limit_ranges[] = {
int min;
int step;
int vals;
int low_sel;
};
static const struct bd70528_linear_range current_limit_ranges[] = {
{ {
.min = 5, .min = 5,
.step = 1, .step = 1,
.vals = 36, .min_sel = 0,
.low_sel = 0, .max_sel = 0x22,
}, },
{ {
.min = 40, .min = 40,
.step = 5, .step = 5,
.vals = 5, .min_sel = 0x23,
.low_sel = 0x23, .max_sel = 0x26,
}, },
{ {
.min = 60, .min = 60,
.step = 20, .step = 20,
.vals = 8, .min_sel = 0x27,
.low_sel = 0x27, .max_sel = 0x2d,
}, },
{ {
.min = 200, .min = 200,
.step = 50, .step = 50,
.vals = 7, .min_sel = 0x2e,
.low_sel = 0x2e, .max_sel = 0x34,
} },
{
.min = 500,
.step = 0,
.min_sel = 0x35,
.max_sel = 0x3f,
},
}; };
/* /*
...@@ -374,18 +374,18 @@ static const struct bd70528_linear_range current_limit_ranges[] = { ...@@ -374,18 +374,18 @@ static const struct bd70528_linear_range current_limit_ranges[] = {
* voltage for low temperatures. The driver currently only reads * voltage for low temperatures. The driver currently only reads
* the charge current at room temperature. We do set both though. * the charge current at room temperature. We do set both though.
*/ */
static const struct bd70528_linear_range warm_charge_curr[] = { static const struct linear_range warm_charge_curr[] = {
{ {
.min = 10, .min = 10,
.step = 10, .step = 10,
.vals = 20, .min_sel = 0,
.low_sel = 0, .max_sel = 0x12,
}, },
{ {
.min = 200, .min = 200,
.step = 25, .step = 25,
.vals = 13, .min_sel = 0x13,
.low_sel = 0x13, .max_sel = 0x1f,
}, },
}; };
...@@ -398,56 +398,6 @@ static const struct bd70528_linear_range warm_charge_curr[] = { ...@@ -398,56 +398,6 @@ static const struct bd70528_linear_range warm_charge_curr[] = {
#define MAX_WARM_CHG_CURR_SEL 0x1f #define MAX_WARM_CHG_CURR_SEL 0x1f
#define MIN_CHG_CURR_SEL 0x0 #define MIN_CHG_CURR_SEL 0x0
static int find_value_for_selector_low(const struct bd70528_linear_range *r,
int selectors, unsigned int sel,
unsigned int *val)
{
int i;
for (i = 0; i < selectors; i++) {
if (r[i].low_sel <= sel && r[i].low_sel + r[i].vals >= sel) {
*val = r[i].min + (sel - r[i].low_sel) * r[i].step;
return 0;
}
}
return -EINVAL;
}
/*
* For BD70528 voltage/current limits we happily accept any value which
* belongs the range. We could check if value matching the selector is
* desired by computing the range min + (sel - sel_low) * range step - but
* I guess it is enough if we use voltage/current which is closest (below)
* the requested?
*/
static int find_selector_for_value_low(const struct bd70528_linear_range *r,
int selectors, unsigned int val,
unsigned int *sel, bool *found)
{
int i;
int ret = -EINVAL;
*found = false;
for (i = 0; i < selectors; i++) {
if (r[i].min <= val) {
if (r[i].min + r[i].step * r[i].vals >= val) {
*found = true;
*sel = r[i].low_sel + (val - r[i].min) /
r[i].step;
ret = 0;
break;
}
/*
* If the range max is smaller than requested
* we can set the max supported value from range
*/
*sel = r[i].low_sel + r[i].vals;
ret = 0;
}
}
return ret;
}
static int get_charge_current(struct bd70528_psy *bdpsy, int *ma) static int get_charge_current(struct bd70528_psy *bdpsy, int *ma)
{ {
unsigned int sel; unsigned int sel;
...@@ -463,9 +413,9 @@ static int get_charge_current(struct bd70528_psy *bdpsy, int *ma) ...@@ -463,9 +413,9 @@ static int get_charge_current(struct bd70528_psy *bdpsy, int *ma)
sel &= BD70528_MASK_CHG_CHG_CURR; sel &= BD70528_MASK_CHG_CHG_CURR;
ret = find_value_for_selector_low(&warm_charge_curr[0], ret = linear_range_get_value_array(&warm_charge_curr[0],
ARRAY_SIZE(warm_charge_curr), sel, ARRAY_SIZE(warm_charge_curr),
ma); sel, ma);
if (ret) { if (ret) {
dev_err(bdpsy->dev, dev_err(bdpsy->dev,
"Unknown charge current value 0x%x\n", "Unknown charge current value 0x%x\n",
...@@ -491,10 +441,9 @@ static int get_current_limit(struct bd70528_psy *bdpsy, int *ma) ...@@ -491,10 +441,9 @@ static int get_current_limit(struct bd70528_psy *bdpsy, int *ma)
sel &= BD70528_MASK_CHG_DCIN_ILIM; sel &= BD70528_MASK_CHG_DCIN_ILIM;
ret = find_value_for_selector_low(&current_limit_ranges[0], ret = linear_range_get_value_array(&current_limit_ranges[0],
ARRAY_SIZE(current_limit_ranges), sel, ARRAY_SIZE(current_limit_ranges),
ma); sel, ma);
if (ret) { if (ret) {
/* Unspecified values mean 500 mA */ /* Unspecified values mean 500 mA */
*ma = 500; *ma = 500;
...@@ -588,15 +537,28 @@ static int set_charge_current(struct bd70528_psy *bdpsy, int ma) ...@@ -588,15 +537,28 @@ static int set_charge_current(struct bd70528_psy *bdpsy, int ma)
goto set; goto set;
} }
ret = find_selector_for_value_low(&warm_charge_curr[0], /*
ARRAY_SIZE(warm_charge_curr), ma, * For BD70528 voltage/current limits we happily accept any value which
&reg, &found); * belongs the range. We could check if value matching the selector is
* desired by computing the range min + (sel - sel_low) * range step - but
* I guess it is enough if we use voltage/current which is closest (below)
* the requested?
*/
ret = linear_range_get_selector_low_array(warm_charge_curr,
ARRAY_SIZE(warm_charge_curr),
ma, &reg, &found);
if (ret) { if (ret) {
dev_err(bdpsy->dev,
"Unsupported charge current %u mA\n", ma);
reg = MIN_CHG_CURR_SEL; reg = MIN_CHG_CURR_SEL;
goto set; goto set;
} }
if (!found) { if (!found) {
/* There was a gap in supported values and we hit it */ /*
* There was a gap in supported values and we hit it.
* Yet a smaller value was found so we use it.
*/
dev_warn(bdpsy->dev, dev_warn(bdpsy->dev,
"Unsupported charge current %u mA\n", ma); "Unsupported charge current %u mA\n", ma);
} }
...@@ -648,17 +610,21 @@ static int set_current_limit(struct bd70528_psy *bdpsy, int ma) ...@@ -648,17 +610,21 @@ static int set_current_limit(struct bd70528_psy *bdpsy, int ma)
goto set; goto set;
} }
ret = find_selector_for_value_low(&current_limit_ranges[0], ret = linear_range_get_selector_low_array(current_limit_ranges,
ARRAY_SIZE(current_limit_ranges), ma, ARRAY_SIZE(current_limit_ranges),
&reg, &found); ma, &reg, &found);
if (ret) { if (ret) {
dev_err(bdpsy->dev, "Unsupported current limit %umA\n", ma);
reg = MIN_CURR_LIMIT_SEL; reg = MIN_CURR_LIMIT_SEL;
goto set; goto set;
} }
if (!found) { if (!found) {
/* There was a gap in supported values and we hit it ?*/ /*
dev_warn(bdpsy->dev, "Unsupported current limit %umA\n", * There was a gap in supported values and we hit it.
ma); * We found a smaller value from ranges and use it.
* Warn user though.
*/
dev_warn(bdpsy->dev, "Unsupported current limit %umA\n", ma);
} }
set: set:
......
// SPDX-License-Identifier: GPL-2.0-only
/*
* ROHM BD99954 charger driver
*
* Copyright (C) 2020 Rohm Semiconductors
* Originally written by:
* Mikko Mutanen <mikko.mutanen@fi.rohmeurope.com>
* Markus Laine <markus.laine@fi.rohmeurope.com>
* Bugs added by:
* Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
*/
/*
* The battery charging profile of BD99954.
*
* Curve (1) represents charging current.
* Curve (2) represents battery voltage.
*
* The BD99954 data sheet divides charging to three phases.
* a) Trickle-charge with constant current (8).
* b) pre-charge with constant current (6)
* c) fast-charge, first with constant current (5) phase. After
* the battery voltage has reached target level (4) we have constant
* voltage phase until charging current has dropped to termination
* level (7)
*
* V ^ ^ I
* . .
* . .
*(4)` `.` ` ` ` ` ` ` ` ` ` ` ` ` ` ----------------------------.
* . :/ .
* . o----+/:/ ` ` ` ` ` ` ` ` ` ` ` ` `.` ` (5)
* . + :: + .
* . + /- -- .
* . +`/- + .
* . o/- -: .
* . .s. +` .
* . .--+ `/ .
* . ..`` + .: .
* . -` + -- .
* . (2) ...`` + :- .
* . ...`` + -: .
*(3)` `.`."" ` ` ` `+-------- ` ` ` ` ` ` `.:` ` ` ` ` ` ` ` ` .` ` (6)
* . + `:. .
* . + -: .
* . + -:. .
* . + .--. .
* . (1) + `.+` ` ` `.` ` (7)
* -..............` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` + ` ` ` .` ` (8)
* . + -
* -------------------------------------------------+++++++++-->
* | trickle | pre | fast |
*
* Details of DT properties for different limits can be found from BD99954
* device tree binding documentation.
*/
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/linear_range.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/power_supply.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/types.h>
#include "bd99954-charger.h"
struct battery_data {
u16 precharge_current; /* Trickle-charge Current */
u16 fc_reg_voltage; /* Fast Charging Regulation Voltage */
u16 voltage_min;
u16 voltage_max;
};
/* Initial field values, converted to initial register values */
struct bd9995x_init_data {
u16 vsysreg_set; /* VSYS Regulation Setting */
u16 ibus_lim_set; /* VBUS input current limitation */
u16 icc_lim_set; /* VCC/VACP Input Current Limit Setting */
u16 itrich_set; /* Trickle-charge Current Setting */
u16 iprech_set; /* Pre-Charge Current Setting */
u16 ichg_set; /* Fast-Charge constant current */
u16 vfastchg_reg_set1; /* Fast Charging Regulation Voltage */
u16 vprechg_th_set; /* Pre-charge Voltage Threshold Setting */
u16 vrechg_set; /* Re-charge Battery Voltage Setting */
u16 vbatovp_set; /* Battery Over Voltage Threshold Setting */
u16 iterm_set; /* Charging termination current */
};
struct bd9995x_state {
u8 online;
u16 chgstm_status;
u16 vbat_vsys_status;
u16 vbus_vcc_status;
};
struct bd9995x_device {
struct i2c_client *client;
struct device *dev;
struct power_supply *charger;
struct regmap *rmap;
struct regmap_field *rmap_fields[F_MAX_FIELDS];
int chip_id;
int chip_rev;
struct bd9995x_init_data init_data;
struct bd9995x_state state;
struct mutex lock; /* Protect state data */
};
static const struct regmap_range bd9995x_readonly_reg_ranges[] = {
regmap_reg_range(CHGSTM_STATUS, SEL_ILIM_VAL),
regmap_reg_range(IOUT_DACIN_VAL, IOUT_DACIN_VAL),
regmap_reg_range(VCC_UCD_STATUS, VCC_IDD_STATUS),
regmap_reg_range(VBUS_UCD_STATUS, VBUS_IDD_STATUS),
regmap_reg_range(CHIP_ID, CHIP_REV),
regmap_reg_range(SYSTEM_STATUS, SYSTEM_STATUS),
regmap_reg_range(IBATP_VAL, VBAT_AVE_VAL),
regmap_reg_range(VTH_VAL, EXTIADP_AVE_VAL),
};
static const struct regmap_access_table bd9995x_writeable_regs = {
.no_ranges = bd9995x_readonly_reg_ranges,
.n_no_ranges = ARRAY_SIZE(bd9995x_readonly_reg_ranges),
};
static const struct regmap_range bd9995x_volatile_reg_ranges[] = {
regmap_reg_range(CHGSTM_STATUS, WDT_STATUS),
regmap_reg_range(VCC_UCD_STATUS, VCC_IDD_STATUS),
regmap_reg_range(VBUS_UCD_STATUS, VBUS_IDD_STATUS),
regmap_reg_range(INT0_STATUS, INT7_STATUS),
regmap_reg_range(SYSTEM_STATUS, SYSTEM_CTRL_SET),
regmap_reg_range(IBATP_VAL, EXTIADP_AVE_VAL), /* Measurement regs */
};
static const struct regmap_access_table bd9995x_volatile_regs = {
.yes_ranges = bd9995x_volatile_reg_ranges,
.n_yes_ranges = ARRAY_SIZE(bd9995x_volatile_reg_ranges),
};
static const struct regmap_range_cfg regmap_range_cfg[] = {
{
.selector_reg = MAP_SET,
.selector_mask = 0xFFFF,
.selector_shift = 0,
.window_start = 0,
.window_len = 0x100,
.range_min = 0 * 0x100,
.range_max = 3 * 0x100,
},
};
static const struct regmap_config bd9995x_regmap_config = {
.reg_bits = 8,
.val_bits = 16,
.reg_stride = 1,
.max_register = 3 * 0x100,
.cache_type = REGCACHE_RBTREE,
.ranges = regmap_range_cfg,
.num_ranges = ARRAY_SIZE(regmap_range_cfg),
.val_format_endian = REGMAP_ENDIAN_LITTLE,
.wr_table = &bd9995x_writeable_regs,
.volatile_table = &bd9995x_volatile_regs,
};
enum bd9995x_chrg_fault {
CHRG_FAULT_NORMAL,
CHRG_FAULT_INPUT,
CHRG_FAULT_THERMAL_SHUTDOWN,
CHRG_FAULT_TIMER_EXPIRED,
};
static int bd9995x_get_prop_batt_health(struct bd9995x_device *bd)
{
int ret, tmp;
ret = regmap_field_read(bd->rmap_fields[F_BATTEMP], &tmp);
if (ret)
return POWER_SUPPLY_HEALTH_UNKNOWN;
/* TODO: Check these against datasheet page 34 */
switch (tmp) {
case ROOM:
return POWER_SUPPLY_HEALTH_GOOD;
case HOT1:
case HOT2:
case HOT3:
return POWER_SUPPLY_HEALTH_OVERHEAT;
case COLD1:
case COLD2:
return POWER_SUPPLY_HEALTH_COLD;
case TEMP_DIS:
case BATT_OPEN:
default:
return POWER_SUPPLY_HEALTH_UNKNOWN;
}
}
static int bd9995x_get_prop_charge_type(struct bd9995x_device *bd)
{
int ret, tmp;
ret = regmap_field_read(bd->rmap_fields[F_CHGSTM_STATE], &tmp);
if (ret)
return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
switch (tmp) {
case CHGSTM_TRICKLE_CHARGE:
case CHGSTM_PRE_CHARGE:
return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
case CHGSTM_FAST_CHARGE:
return POWER_SUPPLY_CHARGE_TYPE_FAST;
case CHGSTM_TOP_OFF:
case CHGSTM_DONE:
case CHGSTM_SUSPEND:
return POWER_SUPPLY_CHARGE_TYPE_NONE;
default: /* Rest of the states are error related, no charging */
return POWER_SUPPLY_CHARGE_TYPE_NONE;
}
}
static bool bd9995x_get_prop_batt_present(struct bd9995x_device *bd)
{
int ret, tmp;
ret = regmap_field_read(bd->rmap_fields[F_BATTEMP], &tmp);
if (ret)
return false;
return tmp != BATT_OPEN;
}
static int bd9995x_get_prop_batt_voltage(struct bd9995x_device *bd)
{
int ret, tmp;
ret = regmap_field_read(bd->rmap_fields[F_VBAT_VAL], &tmp);
if (ret)
return 0;
tmp = min(tmp, 19200);
return tmp * 1000;
}
static int bd9995x_get_prop_batt_current(struct bd9995x_device *bd)
{
int ret, tmp;
ret = regmap_field_read(bd->rmap_fields[F_IBATP_VAL], &tmp);
if (ret)
return 0;
return tmp * 1000;
}
#define DEFAULT_BATTERY_TEMPERATURE 250
static int bd9995x_get_prop_batt_temp(struct bd9995x_device *bd)
{
int ret, tmp;
ret = regmap_field_read(bd->rmap_fields[F_THERM_VAL], &tmp);
if (ret)
return DEFAULT_BATTERY_TEMPERATURE;
return (200 - tmp) * 10;
}
static int bd9995x_power_supply_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
int ret, tmp;
struct bd9995x_device *bd = power_supply_get_drvdata(psy);
struct bd9995x_state state;
mutex_lock(&bd->lock);
state = bd->state;
mutex_unlock(&bd->lock);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
switch (state.chgstm_status) {
case CHGSTM_TRICKLE_CHARGE:
case CHGSTM_PRE_CHARGE:
case CHGSTM_FAST_CHARGE:
case CHGSTM_TOP_OFF:
val->intval = POWER_SUPPLY_STATUS_CHARGING;
break;
case CHGSTM_DONE:
val->intval = POWER_SUPPLY_STATUS_FULL;
break;
case CHGSTM_SUSPEND:
case CHGSTM_TEMPERATURE_ERROR_1:
case CHGSTM_TEMPERATURE_ERROR_2:
case CHGSTM_TEMPERATURE_ERROR_3:
case CHGSTM_TEMPERATURE_ERROR_4:
case CHGSTM_TEMPERATURE_ERROR_5:
case CHGSTM_TEMPERATURE_ERROR_6:
case CHGSTM_TEMPERATURE_ERROR_7:
case CHGSTM_THERMAL_SHUT_DOWN_1:
case CHGSTM_THERMAL_SHUT_DOWN_2:
case CHGSTM_THERMAL_SHUT_DOWN_3:
case CHGSTM_THERMAL_SHUT_DOWN_4:
case CHGSTM_THERMAL_SHUT_DOWN_5:
case CHGSTM_THERMAL_SHUT_DOWN_6:
case CHGSTM_THERMAL_SHUT_DOWN_7:
case CHGSTM_BATTERY_ERROR:
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
default:
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
break;
}
break;
case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = BD9995X_MANUFACTURER;
break;
case POWER_SUPPLY_PROP_ONLINE:
val->intval = state.online;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
ret = regmap_field_read(bd->rmap_fields[F_IBATP_VAL], &tmp);
if (ret)
return ret;
val->intval = tmp * 1000;
break;
case POWER_SUPPLY_PROP_CHARGE_AVG:
ret = regmap_field_read(bd->rmap_fields[F_IBATP_AVE_VAL], &tmp);
if (ret)
return ret;
val->intval = tmp * 1000;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
/*
* Currently the DT uses this property to give the
* target current for fast-charging constant current phase.
* I think it is correct in a sense.
*
* Yet, this prop we read and return here is the programmed
* safety limit for combined input currents. This feels
* also correct in a sense.
*
* However, this results a mismatch to DT value and value
* read from sysfs.
*/
ret = regmap_field_read(bd->rmap_fields[F_SEL_ILIM_VAL], &tmp);
if (ret)
return ret;
val->intval = tmp * 1000;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
if (!state.online) {
val->intval = 0;
break;
}
ret = regmap_field_read(bd->rmap_fields[F_VFASTCHG_REG_SET1],
&tmp);
if (ret)
return ret;
/*
* The actual range : 2560 to 19200 mV. No matter what the
* register says
*/
val->intval = clamp_val(tmp << 4, 2560, 19200);
val->intval *= 1000;
break;
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
ret = regmap_field_read(bd->rmap_fields[F_ITERM_SET], &tmp);
if (ret)
return ret;
/* Start step is 64 mA */
val->intval = tmp << 6;
/* Maximum is 1024 mA - no matter what register says */
val->intval = min(val->intval, 1024);
val->intval *= 1000;
break;
/* Battery properties which we access through charger */
case POWER_SUPPLY_PROP_PRESENT:
val->intval = bd9995x_get_prop_batt_present(bd);
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = bd9995x_get_prop_batt_voltage(bd);
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
val->intval = bd9995x_get_prop_batt_current(bd);
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
val->intval = bd9995x_get_prop_charge_type(bd);
break;
case POWER_SUPPLY_PROP_HEALTH:
val->intval = bd9995x_get_prop_batt_health(bd);
break;
case POWER_SUPPLY_PROP_TEMP:
val->intval = bd9995x_get_prop_batt_temp(bd);
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = "bd99954";
break;
default:
return -EINVAL;
}
return 0;
}
static int bd9995x_get_chip_state(struct bd9995x_device *bd,
struct bd9995x_state *state)
{
int i, ret, tmp;
struct {
struct regmap_field *id;
u16 *data;
} state_fields[] = {
{
bd->rmap_fields[F_CHGSTM_STATE], &state->chgstm_status,
}, {
bd->rmap_fields[F_VBAT_VSYS_STATUS],
&state->vbat_vsys_status,
}, {
bd->rmap_fields[F_VBUS_VCC_STATUS],
&state->vbus_vcc_status,
},
};
for (i = 0; i < ARRAY_SIZE(state_fields); i++) {
ret = regmap_field_read(state_fields[i].id, &tmp);
if (ret)
return ret;
*state_fields[i].data = tmp;
}
if (state->vbus_vcc_status & STATUS_VCC_DET ||
state->vbus_vcc_status & STATUS_VBUS_DET)
state->online = 1;
else
state->online = 0;
return 0;
}
static irqreturn_t bd9995x_irq_handler_thread(int irq, void *private)
{
struct bd9995x_device *bd = private;
int ret, status, mask, i;
unsigned long tmp;
struct bd9995x_state state;
/*
* The bd9995x does not seem to generate big amount of interrupts.
* The logic regarding which interrupts can cause relevant
* status changes seem to be pretty complex.
*
* So lets implement really simple and hopefully bullet-proof handler:
* It does not really matter which IRQ we handle, we just go and
* re-read all interesting statuses + give the framework a nudge.
*
* Other option would be building a _complex_ and error prone logic
* trying to decide what could have been changed (resulting this IRQ
* we are now handling). During the normal operation the BD99954 does
* not seem to be generating much of interrupts so benefit from such
* logic would probably be minimal.
*/
ret = regmap_read(bd->rmap, INT0_STATUS, &status);
if (ret) {
dev_err(bd->dev, "Failed to read IRQ status\n");
return IRQ_NONE;
}
ret = regmap_field_read(bd->rmap_fields[F_INT0_SET], &mask);
if (ret) {
dev_err(bd->dev, "Failed to read IRQ mask\n");
return IRQ_NONE;
}
/* Handle only IRQs that are not masked */
status &= mask;
tmp = status;
/* Lowest bit does not represent any sub-registers */
tmp >>= 1;
/*
* Mask and ack IRQs we will handle (+ the idiot bit)
*/
ret = regmap_field_write(bd->rmap_fields[F_INT0_SET], 0);
if (ret) {
dev_err(bd->dev, "Failed to mask F_INT0\n");
return IRQ_NONE;
}
ret = regmap_write(bd->rmap, INT0_STATUS, status);
if (ret) {
dev_err(bd->dev, "Failed to ack F_INT0\n");
goto err_umask;
}
for_each_set_bit(i, &tmp, 7) {
int sub_status, sub_mask;
int sub_status_reg[] = {
INT1_STATUS, INT2_STATUS, INT3_STATUS, INT4_STATUS,
INT5_STATUS, INT6_STATUS, INT7_STATUS,
};
struct regmap_field *sub_mask_f[] = {
bd->rmap_fields[F_INT1_SET],
bd->rmap_fields[F_INT2_SET],
bd->rmap_fields[F_INT3_SET],
bd->rmap_fields[F_INT4_SET],
bd->rmap_fields[F_INT5_SET],
bd->rmap_fields[F_INT6_SET],
bd->rmap_fields[F_INT7_SET],
};
/* Clear sub IRQs */
ret = regmap_read(bd->rmap, sub_status_reg[i], &sub_status);
if (ret) {
dev_err(bd->dev, "Failed to read IRQ sub-status\n");
goto err_umask;
}
ret = regmap_field_read(sub_mask_f[i], &sub_mask);
if (ret) {
dev_err(bd->dev, "Failed to read IRQ sub-mask\n");
goto err_umask;
}
/* Ack active sub-statuses */
sub_status &= sub_mask;
ret = regmap_write(bd->rmap, sub_status_reg[i], sub_status);
if (ret) {
dev_err(bd->dev, "Failed to ack sub-IRQ\n");
goto err_umask;
}
}
ret = regmap_field_write(bd->rmap_fields[F_INT0_SET], mask);
if (ret)
/* May as well retry once */
goto err_umask;
/* Read whole chip state */
ret = bd9995x_get_chip_state(bd, &state);
if (ret < 0) {
dev_err(bd->dev, "Failed to read chip state\n");
} else {
mutex_lock(&bd->lock);
bd->state = state;
mutex_unlock(&bd->lock);
power_supply_changed(bd->charger);
}
return IRQ_HANDLED;
err_umask:
ret = regmap_field_write(bd->rmap_fields[F_INT0_SET], mask);
if (ret)
dev_err(bd->dev,
"Failed to un-mask F_INT0 - IRQ permanently disabled\n");
return IRQ_NONE;
}
static int __bd9995x_chip_reset(struct bd9995x_device *bd)
{
int ret, state;
int rst_check_counter = 10;
u16 tmp = ALLRST | OTPLD;
ret = regmap_raw_write(bd->rmap, SYSTEM_CTRL_SET, &tmp, 2);
if (ret < 0)
return ret;
do {
ret = regmap_field_read(bd->rmap_fields[F_OTPLD_STATE], &state);
if (ret)
return ret;
msleep(10);
} while (state == 0 && --rst_check_counter);
if (!rst_check_counter) {
dev_err(bd->dev, "chip reset not completed\n");
return -ETIMEDOUT;
}
tmp = 0;
ret = regmap_raw_write(bd->rmap, SYSTEM_CTRL_SET, &tmp, 2);
return ret;
}
static int bd9995x_hw_init(struct bd9995x_device *bd)
{
int ret;
int i;
struct bd9995x_state state;
struct bd9995x_init_data *id = &bd->init_data;
const struct {
enum bd9995x_fields id;
u16 value;
} init_data[] = {
/* Enable the charging trigger after SDP charger attached */
{F_SDP_CHG_TRIG_EN, 1},
/* Enable charging trigger after SDP charger attached */
{F_SDP_CHG_TRIG, 1},
/* Disable charging trigger by BC1.2 detection */
{F_VBUS_BC_DISEN, 1},
/* Disable charging trigger by BC1.2 detection */
{F_VCC_BC_DISEN, 1},
/* Disable automatic limitation of the input current */
{F_ILIM_AUTO_DISEN, 1},
/* Select current limitation when SDP charger attached*/
{F_SDP_500_SEL, 1},
/* Select current limitation when DCP charger attached */
{F_DCP_2500_SEL, 1},
{F_VSYSREG_SET, id->vsysreg_set},
/* Activate USB charging and DC/DC converter */
{F_USB_SUS, 0},
/* DCDC clock: 1200 kHz*/
{F_DCDC_CLK_SEL, 3},
/* Enable charging */
{F_CHG_EN, 1},
/* Disable Input current Limit setting voltage measurement */
{F_EXTIADPEN, 0},
/* Disable input current limiting */
{F_VSYS_PRIORITY, 1},
{F_IBUS_LIM_SET, id->ibus_lim_set},
{F_ICC_LIM_SET, id->icc_lim_set},
/* Charge Termination Current Setting to 0*/
{F_ITERM_SET, id->iterm_set},
/* Trickle-charge Current Setting */
{F_ITRICH_SET, id->itrich_set},
/* Pre-charge Current setting */
{F_IPRECH_SET, id->iprech_set},
/* Fast Charge Current for constant current phase */
{F_ICHG_SET, id->ichg_set},
/* Fast Charge Voltage Regulation Setting */
{F_VFASTCHG_REG_SET1, id->vfastchg_reg_set1},
/* Set Pre-charge Voltage Threshold for trickle charging. */
{F_VPRECHG_TH_SET, id->vprechg_th_set},
{F_VRECHG_SET, id->vrechg_set},
{F_VBATOVP_SET, id->vbatovp_set},
/* Reverse buck boost voltage Setting */
{F_VRBOOST_SET, 0},
/* Disable fast-charging watchdog */
{F_WDT_FST, 0},
/* Disable pre-charging watchdog */
{F_WDT_PRE, 0},
/* Power save off */
{F_POWER_SAVE_MODE, 0},
{F_INT1_SET, INT1_ALL},
{F_INT2_SET, INT2_ALL},
{F_INT3_SET, INT3_ALL},
{F_INT4_SET, INT4_ALL},
{F_INT5_SET, INT5_ALL},
{F_INT6_SET, INT6_ALL},
{F_INT7_SET, INT7_ALL},
};
/*
* Currently we initialize charger to a known state at startup.
* If we want to allow for example the boot code to initialize
* charger we should get rid of this.
*/
ret = __bd9995x_chip_reset(bd);
if (ret < 0)
return ret;
/* Initialize currents/voltages and other parameters */
for (i = 0; i < ARRAY_SIZE(init_data); i++) {
ret = regmap_field_write(bd->rmap_fields[init_data[i].id],
init_data[i].value);
if (ret) {
dev_err(bd->dev, "failed to initialize charger (%d)\n",
ret);
return ret;
}
}
ret = bd9995x_get_chip_state(bd, &state);
if (ret < 0)
return ret;
mutex_lock(&bd->lock);
bd->state = state;
mutex_unlock(&bd->lock);
return 0;
}
static enum power_supply_property bd9995x_power_supply_props[] = {
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
POWER_SUPPLY_PROP_CHARGE_AVG,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
/* Battery props we access through charger */
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_MODEL_NAME,
};
static const struct power_supply_desc bd9995x_power_supply_desc = {
.name = "bd9995x-charger",
.type = POWER_SUPPLY_TYPE_USB,
.properties = bd9995x_power_supply_props,
.num_properties = ARRAY_SIZE(bd9995x_power_supply_props),
.get_property = bd9995x_power_supply_get_property,
};
/*
* Limit configurations for vbus-input-current and vcc-vacp-input-current
* Minimum limit is 0 uA. Max is 511 * 32000 uA = 16352000 uA. This is
* configured by writing a register so that each increment in register
* value equals to 32000 uA limit increment.
*
* Eg, value 0x0 is limit 0, value 0x1 is limit 32000, ...
* Describe the setting in linear_range table.
*/
static const struct linear_range input_current_limit_ranges[] = {
{
.min = 0,
.step = 32000,
.min_sel = 0x0,
.max_sel = 0x1ff,
},
};
/* Possible trickle, pre-charging and termination current values */
static const struct linear_range charging_current_ranges[] = {
{
.min = 0,
.step = 64000,
.min_sel = 0x0,
.max_sel = 0x10,
}, {
.min = 1024000,
.step = 0,
.min_sel = 0x11,
.max_sel = 0x1f,
},
};
/*
* Fast charging voltage regulation, starting re-charging limit
* and battery over voltage protection have same possible values
*/
static const struct linear_range charge_voltage_regulation_ranges[] = {
{
.min = 2560000,
.step = 0,
.min_sel = 0,
.max_sel = 0xA0,
}, {
.min = 2560000,
.step = 16000,
.min_sel = 0xA0,
.max_sel = 0x4B0,
}, {
.min = 19200000,
.step = 0,
.min_sel = 0x4B0,
.max_sel = 0x7FF,
},
};
/* Possible VSYS voltage regulation values */
static const struct linear_range vsys_voltage_regulation_ranges[] = {
{
.min = 2560000,
.step = 0,
.min_sel = 0,
.max_sel = 0x28,
}, {
.min = 2560000,
.step = 64000,
.min_sel = 0x28,
.max_sel = 0x12C,
}, {
.min = 19200000,
.step = 0,
.min_sel = 0x12C,
.max_sel = 0x1FF,
},
};
/* Possible settings for switching from trickle to pre-charging limits */
static const struct linear_range trickle_to_pre_threshold_ranges[] = {
{
.min = 2048000,
.step = 0,
.min_sel = 0,
.max_sel = 0x20,
}, {
.min = 2048000,
.step = 64000,
.min_sel = 0x20,
.max_sel = 0x12C,
}, {
.min = 19200000,
.step = 0,
.min_sel = 0x12C,
.max_sel = 0x1FF
}
};
/* Possible current values for fast-charging constant current phase */
static const struct linear_range fast_charge_current_ranges[] = {
{
.min = 0,
.step = 64000,
.min_sel = 0,
.max_sel = 0xFF,
}
};
struct battery_init {
const char *name;
int *info_data;
const struct linear_range *range;
int ranges;
u16 *data;
};
struct dt_init {
char *prop;
const struct linear_range *range;
int ranges;
u16 *data;
};
static int bd9995x_fw_probe(struct bd9995x_device *bd)
{
int ret;
struct power_supply_battery_info info;
u32 property;
int i;
int regval;
bool found;
struct bd9995x_init_data *init = &bd->init_data;
struct battery_init battery_inits[] = {
{
.name = "trickle-charging current",
.info_data = &info.tricklecharge_current_ua,
.range = &charging_current_ranges[0],
.ranges = 2,
.data = &init->itrich_set,
}, {
.name = "pre-charging current",
.info_data = &info.precharge_current_ua,
.range = &charging_current_ranges[0],
.ranges = 2,
.data = &init->iprech_set,
}, {
.name = "pre-to-trickle charge voltage threshold",
.info_data = &info.precharge_voltage_max_uv,
.range = &trickle_to_pre_threshold_ranges[0],
.ranges = 2,
.data = &init->vprechg_th_set,
}, {
.name = "charging termination current",
.info_data = &info.charge_term_current_ua,
.range = &charging_current_ranges[0],
.ranges = 2,
.data = &init->iterm_set,
}, {
.name = "charging re-start voltage",
.info_data = &info.charge_restart_voltage_uv,
.range = &charge_voltage_regulation_ranges[0],
.ranges = 2,
.data = &init->vrechg_set,
}, {
.name = "battery overvoltage limit",
.info_data = &info.overvoltage_limit_uv,
.range = &charge_voltage_regulation_ranges[0],
.ranges = 2,
.data = &init->vbatovp_set,
}, {
.name = "fast-charging max current",
.info_data = &info.constant_charge_current_max_ua,
.range = &fast_charge_current_ranges[0],
.ranges = 1,
.data = &init->ichg_set,
}, {
.name = "fast-charging voltage",
.info_data = &info.constant_charge_voltage_max_uv,
.range = &charge_voltage_regulation_ranges[0],
.ranges = 2,
.data = &init->vfastchg_reg_set1,
},
};
struct dt_init props[] = {
{
.prop = "rohm,vsys-regulation-microvolt",
.range = &vsys_voltage_regulation_ranges[0],
.ranges = 2,
.data = &init->vsysreg_set,
}, {
.prop = "rohm,vbus-input-current-limit-microamp",
.range = &input_current_limit_ranges[0],
.ranges = 1,
.data = &init->ibus_lim_set,
}, {
.prop = "rohm,vcc-input-current-limit-microamp",
.range = &input_current_limit_ranges[0],
.ranges = 1,
.data = &init->icc_lim_set,
},
};
/*
* The power_supply_get_battery_info() does not support getting values
* from ACPI. Let's fix it if ACPI is required here.
*/
ret = power_supply_get_battery_info(bd->charger, &info);
if (ret < 0)
return ret;
for (i = 0; i < ARRAY_SIZE(battery_inits); i++) {
int val = *battery_inits[i].info_data;
const struct linear_range *range = battery_inits[i].range;
int ranges = battery_inits[i].ranges;
if (val == -EINVAL)
continue;
ret = linear_range_get_selector_low_array(range, ranges, val,
&regval, &found);
if (ret) {
dev_err(bd->dev, "Unsupported value for %s\n",
battery_inits[i].name);
power_supply_put_battery_info(bd->charger, &info);
return -EINVAL;
}
if (!found) {
dev_warn(bd->dev,
"Unsupported value for %s - using smaller\n",
battery_inits[i].name);
}
*(battery_inits[i].data) = regval;
}
power_supply_put_battery_info(bd->charger, &info);
for (i = 0; i < ARRAY_SIZE(props); i++) {
ret = device_property_read_u32(bd->dev, props[i].prop,
&property);
if (ret < 0) {
dev_err(bd->dev, "failed to read %s", props[i].prop);
return ret;
}
ret = linear_range_get_selector_low_array(props[i].range,
props[i].ranges,
property, &regval,
&found);
if (ret) {
dev_err(bd->dev, "Unsupported value for '%s'\n",
props[i].prop);
return -EINVAL;
}
if (!found) {
dev_warn(bd->dev,
"Unsupported value for '%s' - using smaller\n",
props[i].prop);
}
*(props[i].data) = regval;
}
return 0;
}
static void bd9995x_chip_reset(void *bd)
{
__bd9995x_chip_reset(bd);
}
static int bd9995x_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct bd9995x_device *bd;
struct power_supply_config psy_cfg = {};
int ret;
int i;
bd = devm_kzalloc(dev, sizeof(*bd), GFP_KERNEL);
if (!bd)
return -ENOMEM;
bd->client = client;
bd->dev = dev;
psy_cfg.drv_data = bd;
psy_cfg.of_node = dev->of_node;
mutex_init(&bd->lock);
bd->rmap = devm_regmap_init_i2c(client, &bd9995x_regmap_config);
if (IS_ERR(bd->rmap)) {
dev_err(dev, "Failed to setup register access via i2c\n");
return PTR_ERR(bd->rmap);
}
for (i = 0; i < ARRAY_SIZE(bd9995x_reg_fields); i++) {
const struct reg_field *reg_fields = bd9995x_reg_fields;
bd->rmap_fields[i] = devm_regmap_field_alloc(dev, bd->rmap,
reg_fields[i]);
if (IS_ERR(bd->rmap_fields[i])) {
dev_err(dev, "cannot allocate regmap field\n");
return PTR_ERR(bd->rmap_fields[i]);
}
}
i2c_set_clientdata(client, bd);
ret = regmap_field_read(bd->rmap_fields[F_CHIP_ID], &bd->chip_id);
if (ret) {
dev_err(dev, "Cannot read chip ID.\n");
return ret;
}
if (bd->chip_id != BD99954_ID) {
dev_err(dev, "Chip with ID=0x%x, not supported!\n",
bd->chip_id);
return -ENODEV;
}
ret = regmap_field_read(bd->rmap_fields[F_CHIP_REV], &bd->chip_rev);
if (ret) {
dev_err(dev, "Cannot read revision.\n");
return ret;
}
dev_info(bd->dev, "Found BD99954 chip rev %d\n", bd->chip_rev);
/*
* We need to init the psy before we can call
* power_supply_get_battery_info() for it
*/
bd->charger = devm_power_supply_register(bd->dev,
&bd9995x_power_supply_desc,
&psy_cfg);
if (IS_ERR(bd->charger)) {
dev_err(dev, "Failed to register power supply\n");
return PTR_ERR(bd->charger);
}
ret = bd9995x_fw_probe(bd);
if (ret < 0) {
dev_err(dev, "Cannot read device properties.\n");
return ret;
}
ret = bd9995x_hw_init(bd);
if (ret < 0) {
dev_err(dev, "Cannot initialize the chip.\n");
return ret;
}
ret = devm_add_action_or_reset(dev, bd9995x_chip_reset, bd);
if (ret)
return ret;
return devm_request_threaded_irq(dev, client->irq, NULL,
bd9995x_irq_handler_thread,
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
BD9995X_IRQ_PIN, bd);
}
static const struct of_device_id bd9995x_of_match[] = {
{ .compatible = "rohm,bd99954", },
{ }
};
MODULE_DEVICE_TABLE(of, bd9995x_of_match);
static struct i2c_driver bd9995x_driver = {
.driver = {
.name = "bd9995x-charger",
.of_match_table = bd9995x_of_match,
},
.probe_new = bd9995x_probe,
};
module_i2c_driver(bd9995x_driver);
MODULE_AUTHOR("Laine Markus <markus.laine@fi.rohmeurope.com>");
MODULE_DESCRIPTION("ROHM BD99954 charger driver");
MODULE_LICENSE("GPL");
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2020 ROHM Semiconductors */
#ifndef BD99954_CHARGER_H
#define BD99954_CHARGER_H
#include <linux/regmap.h>
#define BD9995X_MANUFACTURER "Rohm Semiconductor"
#define BD9995X_IRQ_PIN "bd9995x_irq"
#define BD9995X_VSYS_PRECHARGE_OFFSET_MV 200
#define BD99954_ID 0x346
#define BD99955_ID 0x221
#define BD99956_ID 0x331
/* Battery Charger Commands */
#define CHARGING_CURRENT 0x14
#define CHARGING_VOLTAGE 0x15
#define PROTECT_SET 0x3E
#define MAP_SET 0x3F
/* Extended commands */
#define CHGSTM_STATUS 0x100
#define VBAT_VSYS_STATUS 0x101
#define VBUS_VCC_STATUS 0x102
#define CHGOP_STATUS 0x103
#define WDT_STATUS 0x104
#define CUR_ILIM_VAL 0x105
#define SEL_ILIM_VAL 0x106
#define IBUS_LIM_SET 0x107
#define ICC_LIM_SET 0x108
#define IOTG_LIM_SET 0x109
#define VIN_CTRL_SET 0x10A
#define CHGOP_SET1 0x10B
#define CHGOP_SET2 0x10C
#define VBUSCLPS_TH_SET 0x10D
#define VCCCLPS_TH_SET 0x10E
#define CHGWDT_SET 0x10F
#define BATTWDT_SET 0x110
#define VSYSREG_SET 0x111
#define VSYSVAL_THH_SET 0x112
#define VSYSVAL_THL_SET 0x113
#define ITRICH_SET 0x114
#define IPRECH_SET 0x115
#define ICHG_SET 0x116
#define ITERM_SET 0x117
#define VPRECHG_TH_SET 0x118
#define VRBOOST_SET 0x119
#define VFASTCHG_REG_SET1 0x11A
#define VFASTCHG_REG_SET2 0x11B
#define VFASTCHG_REG_SET3 0x11C
#define VRECHG_SET 0x11D
#define VBATOVP_SET 0x11E
#define IBATSHORT_SET 0x11F
#define PROCHOT_CTRL_SET 0x120
#define PROCHOT_ICRIT_SET 0x121
#define PROCHOT_INORM_SET 0x122
#define PROCHOT_IDCHG_SET 0x123
#define PROCHOT_VSYS_SET 0x124
#define PMON_IOUT_CTRL_SET 0x125
#define PMON_DACIN_VAL 0x126
#define IOUT_DACIN_VAL 0x127
#define VCC_UCD_SET 0x128
#define VCC_UCD_STATUS 0x129
#define VCC_IDD_STATUS 0x12A
#define VCC_UCD_FCTRL_SET 0x12B
#define VCC_UCD_FCTRL_EN 0x12C
#define VBUS_UCD_SET 0x130
#define VBUS_UCD_STATUS 0x131
#define VBUS_IDD_STATUS 0x132
#define VBUS_UCD_FCTRL_SET 0x133
#define VBUS_UCD_FCTRL_EN 0x134
#define CHIP_ID 0x138
#define CHIP_REV 0x139
#define IC_SET1 0x13A
#define IC_SET2 0x13B
#define SYSTEM_STATUS 0x13C
#define SYSTEM_CTRL_SET 0x13D
#define VM_CTRL_SET 0x140
#define THERM_WINDOW_SET1 0x141
#define THERM_WINDOW_SET2 0x142
#define THERM_WINDOW_SET3 0x143
#define THERM_WINDOW_SET4 0x144
#define THERM_WINDOW_SET5 0x145
#define IBATP_TH_SET 0x146
#define IBATM_TH_SET 0x147
#define VBAT_TH_SET 0x148
#define THERM_TH_SET 0x149
#define IACP_TH_SET 0x14A
#define VACP_TH_SET 0x14B
#define VBUS_TH_SET 0x14C
#define VCC_TH_SET 0x14D
#define VSYS_TH_SET 0x14E
#define EXTIADP_TH_SET 0x14F
#define IBATP_VAL 0x150
#define IBATP_AVE_VAL 0x151
#define IBATM_VAL 0x152
#define IBATM_AVE_VAL 0x153
#define VBAT_VAL 0x154
#define VBAT_AVE_VAL 0x155
#define THERM_VAL 0x156
#define VTH_VAL 0x157
#define IACP_VAL 0x158
#define IACP_AVE_VAL 0x159
#define VACP_VAL 0x15A
#define VACP_AVE_VAL 0x15B
#define VBUS_VAL 0x15C
#define VBUS_AVE_VAL 0x15D
#define VCC_VAL 0x15E
#define VCC_AVE_VAL 0x15F
#define VSYS_VAL 0x160
#define VSYS_AVE_VAL 0x161
#define EXTIADP_VAL 0x162
#define EXTIADP_AVE_VAL 0x163
#define VACPCLPS_TH_SET 0x164
#define INT0_SET 0x168
#define INT1_SET 0x169
#define INT2_SET 0x16A
#define INT3_SET 0x16B
#define INT4_SET 0x16C
#define INT5_SET 0x16D
#define INT6_SET 0x16E
#define INT7_SET 0x16F
#define INT0_STATUS 0x170
#define INT1_STATUS 0x171
#define INT2_STATUS 0x172
#define INT3_STATUS 0x173
#define INT4_STATUS 0x174
#define INT5_STATUS 0x175
#define INT6_STATUS 0x176
#define INT7_STATUS 0x177
#define OTPREG0 0x17A
#define OTPREG1 0x17B
#define SMBREG 0x17C
#define DEBUG_MODE_SET 0x17F
#define DEBUG0x14 0x214
#define DEBUG0x1A 0x21A
enum bd9995x_fields {
F_PREV_CHGSTM_STATE, F_CHGSTM_STATE,
F_VBAT_VSYS_STATUS,
F_VBUS_VCC_STATUS,
F_BATTEMP, F_VRECHG_DET, F_RBOOST_UV, F_RBOOSTS,
F_THERMWDT_VAL, F_CHGWDT_VAL,
F_CUR_ILIM_VAL,
F_SEL_ILIM_VAL,
F_IBUS_LIM_SET,
F_ICC_LIM_SET,
F_IOTG_LIM_SET,
F_OTG_BOTH_EN,
F_VRBOOST_TRIG,
F_VRBOOST_EN,
F_PP_BOTH_THRU,
F_VIN_ORD,
F_VBUS_EN,
F_VCC_EN,
F_VSYS_PRIORITY,
F_PPC_SUB_CAP,
F_PPC_CAP,
F_DCP_2500_SEL,
F_SDP_500_SEL,
F_ILIM_AUTO_DISEN,
F_VCC_BC_DISEN,
F_VBUS_BC_DISEN,
F_SDP_CHG_TRIG_EN,
F_SDP_CHG_TRIG,
F_AUTO_TOF,
F_AUTO_FST,
F_AUTO_RECH,
F_ILIM_RESET_EN,
F_DCDC_1MS_SEL,
F_SEL_ILIM_DIV,
F_BATT_LEARN,
F_CHG_EN,
F_USB_SUS,
F_CHOP_SS_INIT,
F_CHOP_ALL_INIT,
F_DCDC_CLK_SEL,
F_CHOP_SS,
F_CHOP_ALL,
F_VBUSCLPS_TH_SET,
F_VCCCLPS_TH_SET,
F_WDT_FST,
F_WDT_PRE,
F_WDT_IBAT_SHORT,
F_WDT_THERM,
F_VSYSREG_SET,
F_VSYSVAL_THH_SET,
F_VSYSVAL_THL_SET,
F_ITRICH_SET,
F_IPRECH_SET,
F_ICHG_SET,
F_ITERM_SET,
F_VPRECHG_TH_SET,
F_VRBOOST_SET,
F_VFASTCHG_REG_SET1,
F_VFASTCHG_REG_SET2,
F_VFASTCHG_REG_SET3,
F_VRECHG_SET,
F_VBATOVP_SET,
F_IBATM_SHORT_SET,
F_PROCHOT_DG_SET,
F_PROCHOT_ICRIT_DG_SET,
F_PROCHOT_IDCHG_DG_SET,
F_PROCHOT_EN,
F_PROCHOT_ICRIT_SET,
F_PROCHOT_INORM_SET,
F_PROCHOT_IDCHG_SET,
F_PROCHOT_VSYS_SET,
F_IMON_INSEL,
F_PMON_INSEL,
F_IOUT_OUT_EN,
F_IOUT_SOURCE_SEL,
F_IOUT_GAIN_SET,
F_PMON_OUT_EN,
F_PMON_GAIN_SET,
F_PMON_DACIN_VAL,
F_IOUT_DACIN_VAL,
F_VCC_BCSRETRY,
F_VCC_ADCRTRY,
F_VCC_USBDETEN,
F_VCC_IDRDETEN,
F_VCC_ENUMRDY,
F_VCC_ADCPOLEN,
F_VCC_DCDMODE,
F_VCC_USB_SW_EN,
F_VCC_USB_SW,
F_VCC_DCDFAIL,
F_VCC_CHGPORT,
F_VCC_PUPDET,
F_VCC_VBUS_VLD,
F_VCC_CHGDET,
F_VCC_OTGDET,
F_VCC_VBINOP,
F_VCC_EXTID,
F_VCC_IDRDET,
F_VCC_INDO,
F_VCC_UCDSWEN,
F_VCC_RREF_EN,
F_VCC_DPPU_EN,
F_VCC_DPREF_EN,
F_VCC_DMREF_EN,
F_VCC_DPDET_EN,
F_VCC_DMDET_EN,
F_VCC_DPSINK_EN,
F_VCC_DMSINK_EN,
F_VCC_DP_BUFF_EN,
F_VCC_DM_BUFF_EN,
F_VCC_EXTCLKENBL,
F_VCC_PLSTESTEN,
F_VCC_UCDSWEN_TSTENB,
F_VCC_RREF_EN_TSTENB,
F_VCC_DPPU_EN_TSTENB,
F_VCC_DPREF_EN_TSTENB,
F_VCC_DMREF_EN_TSTENB,
F_VCC_DPDET_EN_TSTENB,
F_VCC_DMDET_EN_TSTENB,
F_VCC_DPSINK_EN_TSTENB,
F_VCC_DMSINK_EN_TSTENB,
F_VCC_DP_BUFF_EN_TSTENB,
F_VCC_DM_BUFF_EN_TSTENB,
F_VBUS_BCSRETRY,
F_VBUS_ADCRTRY,
F_VBUS_USBDETEN,
F_VBUS_IDRDETEN,
F_VBUS_ENUMRDY,
F_VBUS_ADCPOLEN,
F_VBUS_DCDMODE,
F_VBUS_USB_SW_EN,
F_VBUS_USB_SW,
F_VBUS_DCDFAIL,
F_VBUS_CHGPORT,
F_VBUS_PUPDET,
F_VBUS_VBUS_VLD,
F_VBUS_CHGDET,
F_VBUS_OTGDET,
F_VBUS_VBINOP,
F_VBUS_EXTID,
F_VBUS_IDRDET,
F_VBUS_INDO,
F_VBUS_UCDSWEN,
F_VBUS_RREF_EN,
F_VBUS_DPPU_EN,
F_VBUS_DPREF_EN,
F_VBUS_DMREF_EN,
F_VBUS_DPDET_EN,
F_VBUS_DMDET_EN,
F_VBUS_DPSINK_EN,
F_VBUS_DMSINK_EN,
F_VBUS_DP_BUFF_EN,
F_VBUS_DM_BUFF_EN,
F_VBUS_EXTCLKENBL,
F_VBUS_PLSTESTEN,
F_VBUS_UCDSWEN_TSTENB,
F_VBUS_RREF_EN_TSTENB,
F_VBUS_DPPU_EN_TSTENB,
F_VBUS_DPREF_EN_TSTENB,
F_VBUS_DMREF_EN_TSTENB,
F_VBUS_DPDET_EN_TSTENB,
F_VBUS_DMDET_EN_TSTENB,
F_VBUS_DPSINK_EN_TSTENB,
F_VBUS_DMSINK_EN_TSTENB,
F_VBUS_DP_BUFF_EN_TSTENB,
F_VBUS_DM_BUFF_EN_TSTENB,
F_CHIP_ID,
F_CHIP_REV,
F_ONE_CELL_MODE,
F_cell,
F_VACP_AUTO_DISCHG,
F_VACP_LOAD,
F_ACOK_POL,
F_ACOK_DISEN,
F_DEBUG_SET1,
F_DEBUG_SET0,
F_MONRST_STATE,
F_ALMRST_STATE,
F_CHGRST_STATE,
F_OTPLD_STATE,
F_ALLRST_STATE,
F_PROTECT_SET,
F_MAP_SET,
F_ADCINTERVAL,
F_ADCMOD,
F_ADCTMOD,
F_EXTIADPEN,
F_VSYSENB,
F_VCCENB,
F_VBUSENB,
F_VACPENB,
F_IACPENB,
F_THERMENB,
F_VBATENB,
F_IBATMENB,
F_IBATPENB,
F_TMPTHR1B,
F_TMPTHR1A,
F_TMPTHR2B,
F_TMPTHR2A,
F_TMPTHR3B,
F_TMPTHR3A,
F_TMPTHR4B,
F_TMPTHR4A,
F_TMPTHR5B,
F_TMPTHR5A,
F_IBATP_TH_SET,
F_IBATM_TH_SET,
F_VBAT_TH_SET,
F_THERM_TH_SET,
F_IACP_TH_SET,
F_VACP_TH_SET,
F_VBUS_TH_SET,
F_VCC_TH_SET,
F_VSYS_TH_SET,
F_EXTIADP_TH_SET,
F_IBATP_VAL,
F_IBATP_AVE_VAL,
F_IBATM_VAL,
F_IBATM_AVE_VAL,
F_VBAT_VAL,
F_VBAT_AVE_VAL,
F_THERM_VAL,
F_VTH_VAL,
F_IACP_VAL,
F_IACP_AVE_VAL,
F_VACP_VAL,
F_VACP_AVE_VAL,
F_VBUS_VAL,
F_VBUS_AVE_VAL,
F_VCC_VAL,
F_VCC_AVE_VAL,
F_VSYS_VAL,
F_VSYS_AVE_VAL,
F_EXTIADP_VAL,
F_EXTIADP_AVE_VAL,
F_VACPCLPS_TH_SET,
F_INT7_SET,
F_INT6_SET,
F_INT5_SET,
F_INT4_SET,
F_INT3_SET,
F_INT2_SET,
F_INT1_SET,
F_INT0_SET,
F_VBUS_RBUV_DET,
F_VBUS_RBUV_RES,
F_VBUS_TH_DET,
F_VBUS_TH_RES,
F_VBUS_IIN_MOD,
F_VBUS_OV_DET,
F_VBUS_OV_RES,
F_VBUS_CLPS_DET,
F_VBUS_CLPS,
F_VBUS_DET,
F_VBUS_RES,
F_VCC_RBUV_DET,
F_VCC_RBUV_RES,
F_VCC_TH_DET,
F_VCC_TH_RES,
F_VCC_IIN_MOD,
F_VCC_OVP_DET,
F_VCC_OVP_RES,
F_VCC_CLPS_DET,
F_VCC_CLPS_RES,
F_VCC_DET,
F_VCC_RES,
F_TH_DET,
F_TH_RMV,
F_TMP_OUT_DET,
F_TMP_OUT_RES,
F_VBAT_TH_DET,
F_VBAT_TH_RES,
F_IBAT_SHORT_DET,
F_IBAT_SHORT_RES,
F_VBAT_OV_DET,
F_VBAT_OV_RES,
F_BAT_ASSIST_DET,
F_BAT_ASSIST_RES,
F_VSYS_TH_DET,
F_VSYS_TH_RES,
F_VSYS_OV_DET,
F_VSYS_OV_RES,
F_VSYS_SHT_DET,
F_VSYS_SHT_RES,
F_VSYS_UV_DET,
F_VSYS_UV_RES,
F_OTP_LOAD_DONE,
F_PWR_ON,
F_EXTIADP_TRNS,
F_EXTIADP_TH_DET,
F_EXIADP_TH_RES,
F_BAT_MNT_DET,
F_BAT_MNT_RES,
F_TSD_DET,
F_TSD_RES,
F_CHGWDT_EXP,
F_THERMWDT_EXP,
F_TMP_TRNS,
F_CHG_TRNS,
F_VBUS_UCD_PORT_DET,
F_VBUS_UCD_UCHG_DET,
F_VBUS_UCD_URID_RMV,
F_VBUS_UCD_OTG_DET,
F_VBUS_UCD_URID_MOD,
F_VCC_UCD_PORT_DET,
F_VCC_UCD_UCHG_DET,
F_VCC_UCD_URID_RMV,
F_VCC_UCD_OTG_DET,
F_VCC_UCD_URID_MOD,
F_PROCHOT_DET,
F_PROCHOT_RES,
F_VACP_DET,
F_VACP_RES,
F_VACP_TH_DET,
F_VACP_TH_RES,
F_IACP_TH_DET,
F_IACP_THE_RES,
F_THERM_TH_DET,
F_THERM_TH_RES,
F_IBATM_TH_DET,
F_IBATM_TH_RES,
F_IBATP_TH_DET,
F_IBATP_TH_RES,
F_INT7_STATUS,
F_INT6_STATUS,
F_INT5_STATUS,
F_INT4_STATUS,
F_INT3_STATUS,
F_INT2_STATUS,
F_INT1_STATUS,
F_INT0_STATUS,
F_ILIM_DECREASE,
F_RESERVE_OTPREG1,
F_POWER_SAVE_MODE,
F_DEBUG_MODE_SET,
F_DEBUG0x14,
F_DEBUG0x1A,
F_MAX_FIELDS
};
static const struct reg_field bd9995x_reg_fields[] = {
[F_PREV_CHGSTM_STATE] = REG_FIELD(CHGSTM_STATUS, 8, 14),
[F_CHGSTM_STATE] = REG_FIELD(CHGSTM_STATUS, 0, 6),
[F_VBAT_VSYS_STATUS] = REG_FIELD(VBAT_VSYS_STATUS, 0, 15),
[F_VBUS_VCC_STATUS] = REG_FIELD(VBUS_VCC_STATUS, 0, 12),
[F_BATTEMP] = REG_FIELD(CHGOP_STATUS, 8, 10),
[F_VRECHG_DET] = REG_FIELD(CHGOP_STATUS, 6, 6),
[F_RBOOST_UV] = REG_FIELD(CHGOP_STATUS, 1, 1),
[F_RBOOSTS] = REG_FIELD(CHGOP_STATUS, 0, 0),
[F_THERMWDT_VAL] = REG_FIELD(WDT_STATUS, 8, 15),
[F_CHGWDT_VAL] = REG_FIELD(WDT_STATUS, 0, 7),
[F_CUR_ILIM_VAL] = REG_FIELD(CUR_ILIM_VAL, 0, 13),
[F_SEL_ILIM_VAL] = REG_FIELD(SEL_ILIM_VAL, 0, 13),
[F_IBUS_LIM_SET] = REG_FIELD(IBUS_LIM_SET, 5, 13),
[F_ICC_LIM_SET] = REG_FIELD(ICC_LIM_SET, 5, 13),
[F_IOTG_LIM_SET] = REG_FIELD(IOTG_LIM_SET, 5, 13),
[F_OTG_BOTH_EN] = REG_FIELD(VIN_CTRL_SET, 15, 15),
[F_VRBOOST_TRIG] = REG_FIELD(VIN_CTRL_SET, 14, 14),
[F_VRBOOST_EN] = REG_FIELD(VIN_CTRL_SET, 12, 13),
[F_PP_BOTH_THRU] = REG_FIELD(VIN_CTRL_SET, 11, 11),
[F_VIN_ORD] = REG_FIELD(VIN_CTRL_SET, 7, 7),
[F_VBUS_EN] = REG_FIELD(VIN_CTRL_SET, 6, 6),
[F_VCC_EN] = REG_FIELD(VIN_CTRL_SET, 5, 5),
[F_VSYS_PRIORITY] = REG_FIELD(VIN_CTRL_SET, 4, 4),
[F_PPC_SUB_CAP] = REG_FIELD(VIN_CTRL_SET, 2, 3),
[F_PPC_CAP] = REG_FIELD(VIN_CTRL_SET, 0, 1),
[F_DCP_2500_SEL] = REG_FIELD(CHGOP_SET1, 15, 15),
[F_SDP_500_SEL] = REG_FIELD(CHGOP_SET1, 14, 14),
[F_ILIM_AUTO_DISEN] = REG_FIELD(CHGOP_SET1, 13, 13),
[F_VCC_BC_DISEN] = REG_FIELD(CHGOP_SET1, 11, 11),
[F_VBUS_BC_DISEN] = REG_FIELD(CHGOP_SET1, 10, 10),
[F_SDP_CHG_TRIG_EN] = REG_FIELD(CHGOP_SET1, 9, 9),
[F_SDP_CHG_TRIG] = REG_FIELD(CHGOP_SET1, 8, 8),
[F_AUTO_TOF] = REG_FIELD(CHGOP_SET1, 6, 6),
[F_AUTO_FST] = REG_FIELD(CHGOP_SET1, 5, 5),
[F_AUTO_RECH] = REG_FIELD(CHGOP_SET1, 3, 3),
[F_ILIM_RESET_EN] = REG_FIELD(CHGOP_SET2, 14, 14),
[F_DCDC_1MS_SEL] = REG_FIELD(CHGOP_SET2, 12, 13),
[F_SEL_ILIM_DIV] = REG_FIELD(CHGOP_SET2, 10, 10),
[F_BATT_LEARN] = REG_FIELD(CHGOP_SET2, 8, 8),
[F_CHG_EN] = REG_FIELD(CHGOP_SET2, 7, 7),
[F_USB_SUS] = REG_FIELD(CHGOP_SET2, 6, 6),
[F_CHOP_SS_INIT] = REG_FIELD(CHGOP_SET2, 5, 5),
[F_CHOP_ALL_INIT] = REG_FIELD(CHGOP_SET2, 4, 4),
[F_DCDC_CLK_SEL] = REG_FIELD(CHGOP_SET2, 2, 3),
[F_CHOP_SS] = REG_FIELD(CHGOP_SET2, 1, 1),
[F_CHOP_ALL] = REG_FIELD(CHGOP_SET2, 0, 0),
[F_VBUSCLPS_TH_SET] = REG_FIELD(VBUSCLPS_TH_SET, 7, 14),
[F_VCCCLPS_TH_SET] = REG_FIELD(VCCCLPS_TH_SET, 7, 14),
[F_WDT_FST] = REG_FIELD(CHGWDT_SET, 8, 15),
[F_WDT_PRE] = REG_FIELD(CHGWDT_SET, 0, 7),
[F_WDT_IBAT_SHORT] = REG_FIELD(BATTWDT_SET, 8, 15),
[F_WDT_THERM] = REG_FIELD(BATTWDT_SET, 0, 7),
[F_VSYSREG_SET] = REG_FIELD(VSYSREG_SET, 6, 14),
[F_VSYSVAL_THH_SET] = REG_FIELD(VSYSVAL_THH_SET, 6, 14),
[F_VSYSVAL_THL_SET] = REG_FIELD(VSYSVAL_THL_SET, 6, 14),
[F_ITRICH_SET] = REG_FIELD(ITRICH_SET, 6, 10),
[F_IPRECH_SET] = REG_FIELD(IPRECH_SET, 6, 10),
[F_ICHG_SET] = REG_FIELD(ICHG_SET, 6, 13),
[F_ITERM_SET] = REG_FIELD(ITERM_SET, 6, 10),
[F_VPRECHG_TH_SET] = REG_FIELD(VPRECHG_TH_SET, 6, 14),
[F_VRBOOST_SET] = REG_FIELD(VRBOOST_SET, 6, 14),
[F_VFASTCHG_REG_SET1] = REG_FIELD(VFASTCHG_REG_SET1, 4, 14),
[F_VFASTCHG_REG_SET2] = REG_FIELD(VFASTCHG_REG_SET2, 4, 14),
[F_VFASTCHG_REG_SET3] = REG_FIELD(VFASTCHG_REG_SET3, 4, 14),
[F_VRECHG_SET] = REG_FIELD(VRECHG_SET, 4, 14),
[F_VBATOVP_SET] = REG_FIELD(VBATOVP_SET, 4, 14),
[F_IBATM_SHORT_SET] = REG_FIELD(IBATSHORT_SET, 0, 14),
[F_PROCHOT_DG_SET] = REG_FIELD(PROCHOT_CTRL_SET, 14, 15),
[F_PROCHOT_ICRIT_DG_SET] = REG_FIELD(PROCHOT_CTRL_SET, 10, 11),
[F_PROCHOT_IDCHG_DG_SET] = REG_FIELD(PROCHOT_CTRL_SET, 8, 9),
[F_PROCHOT_EN] = REG_FIELD(PROCHOT_CTRL_SET, 0, 4),
[F_PROCHOT_ICRIT_SET] = REG_FIELD(PROCHOT_ICRIT_SET, 0, 14),
[F_PROCHOT_INORM_SET] = REG_FIELD(PROCHOT_INORM_SET, 0, 14),
[F_PROCHOT_IDCHG_SET] = REG_FIELD(PROCHOT_IDCHG_SET, 0, 14),
[F_PROCHOT_VSYS_SET] = REG_FIELD(PROCHOT_VSYS_SET, 0, 14),
[F_IMON_INSEL] = REG_FIELD(PMON_IOUT_CTRL_SET, 9, 9),
[F_PMON_INSEL] = REG_FIELD(PMON_IOUT_CTRL_SET, 8, 8),
[F_IOUT_OUT_EN] = REG_FIELD(PMON_IOUT_CTRL_SET, 7, 7),
[F_IOUT_SOURCE_SEL] = REG_FIELD(PMON_IOUT_CTRL_SET, 6, 6),
[F_IOUT_GAIN_SET] = REG_FIELD(PMON_IOUT_CTRL_SET, 4, 5),
[F_PMON_OUT_EN] = REG_FIELD(PMON_IOUT_CTRL_SET, 3, 3),
[F_PMON_GAIN_SET] = REG_FIELD(PMON_IOUT_CTRL_SET, 0, 2),
[F_PMON_DACIN_VAL] = REG_FIELD(PMON_DACIN_VAL, 0, 9),
[F_IOUT_DACIN_VAL] = REG_FIELD(IOUT_DACIN_VAL, 0, 11),
[F_VCC_BCSRETRY] = REG_FIELD(VCC_UCD_SET, 12, 12),
[F_VCC_ADCRTRY] = REG_FIELD(VCC_UCD_SET, 8, 8),
[F_VCC_USBDETEN] = REG_FIELD(VCC_UCD_SET, 7, 7),
[F_VCC_IDRDETEN] = REG_FIELD(VCC_UCD_SET, 6, 6),
[F_VCC_ENUMRDY] = REG_FIELD(VCC_UCD_SET, 5, 5),
[F_VCC_ADCPOLEN] = REG_FIELD(VCC_UCD_SET, 4, 4),
[F_VCC_DCDMODE] = REG_FIELD(VCC_UCD_SET, 3, 3),
[F_VCC_USB_SW_EN] = REG_FIELD(VCC_UCD_SET, 1, 1),
[F_VCC_USB_SW] = REG_FIELD(VCC_UCD_SET, 0, 0),
[F_VCC_DCDFAIL] = REG_FIELD(VCC_UCD_STATUS, 15, 15),
[F_VCC_CHGPORT] = REG_FIELD(VCC_UCD_STATUS, 12, 13),
[F_VCC_PUPDET] = REG_FIELD(VCC_UCD_STATUS, 11, 11),
[F_VCC_VBUS_VLD] = REG_FIELD(VCC_UCD_STATUS, 7, 7),
[F_VCC_CHGDET] = REG_FIELD(VCC_UCD_STATUS, 6, 6),
[F_VCC_OTGDET] = REG_FIELD(VCC_UCD_STATUS, 3, 3),
[F_VCC_VBINOP] = REG_FIELD(VCC_IDD_STATUS, 6, 6),
[F_VCC_EXTID] = REG_FIELD(VCC_IDD_STATUS, 5, 5),
[F_VCC_IDRDET] = REG_FIELD(VCC_IDD_STATUS, 4, 4),
[F_VCC_INDO] = REG_FIELD(VCC_IDD_STATUS, 0, 3),
[F_VCC_UCDSWEN] = REG_FIELD(VCC_UCD_FCTRL_SET, 10, 10),
[F_VCC_RREF_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 9, 9),
[F_VCC_DPPU_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 8, 8),
[F_VCC_DPREF_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 7, 7),
[F_VCC_DMREF_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 6, 6),
[F_VCC_DPDET_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 5, 5),
[F_VCC_DMDET_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 4, 4),
[F_VCC_DPSINK_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 3, 3),
[F_VCC_DMSINK_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 2, 2),
[F_VCC_DP_BUFF_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 1, 1),
[F_VCC_DM_BUFF_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 0, 0),
[F_VCC_EXTCLKENBL] = REG_FIELD(VCC_UCD_FCTRL_EN, 15, 15),
[F_VCC_PLSTESTEN] = REG_FIELD(VCC_UCD_FCTRL_EN, 14, 14),
[F_VCC_UCDSWEN_TSTENB] = REG_FIELD(VCC_UCD_FCTRL_EN, 10, 10),
[F_VCC_RREF_EN_TSTENB] = REG_FIELD(VCC_UCD_FCTRL_EN, 9, 9),
[F_VCC_DPPU_EN_TSTENB] = REG_FIELD(VCC_UCD_FCTRL_EN, 8, 8),
[F_VCC_DPREF_EN_TSTENB] = REG_FIELD(VCC_UCD_FCTRL_EN, 7, 7),
[F_VCC_DMREF_EN_TSTENB] = REG_FIELD(VCC_UCD_FCTRL_EN, 6, 6),
[F_VCC_DPDET_EN_TSTENB] = REG_FIELD(VCC_UCD_FCTRL_EN, 5, 5),
[F_VCC_DMDET_EN_TSTENB] = REG_FIELD(VCC_UCD_FCTRL_EN, 4, 4),
[F_VCC_DPSINK_EN_TSTENB] = REG_FIELD(VCC_UCD_FCTRL_EN, 3, 3),
[F_VCC_DMSINK_EN_TSTENB] = REG_FIELD(VCC_UCD_FCTRL_EN, 2, 2),
[F_VCC_DP_BUFF_EN_TSTENB] = REG_FIELD(VCC_UCD_FCTRL_EN, 1, 1),
[F_VCC_DM_BUFF_EN_TSTENB] = REG_FIELD(VCC_UCD_FCTRL_EN, 0, 0),
[F_VBUS_BCSRETRY] = REG_FIELD(VBUS_UCD_SET, 12, 12),
[F_VBUS_ADCRTRY] = REG_FIELD(VBUS_UCD_SET, 8, 8),
[F_VBUS_USBDETEN] = REG_FIELD(VBUS_UCD_SET, 7, 7),
[F_VBUS_IDRDETEN] = REG_FIELD(VBUS_UCD_SET, 6, 6),
[F_VBUS_ENUMRDY] = REG_FIELD(VBUS_UCD_SET, 5, 5),
[F_VBUS_ADCPOLEN] = REG_FIELD(VBUS_UCD_SET, 4, 4),
[F_VBUS_DCDMODE] = REG_FIELD(VBUS_UCD_SET, 3, 3),
[F_VBUS_USB_SW_EN] = REG_FIELD(VBUS_UCD_SET, 1, 1),
[F_VBUS_USB_SW] = REG_FIELD(VBUS_UCD_SET, 0, 0),
[F_VBUS_DCDFAIL] = REG_FIELD(VBUS_UCD_STATUS, 15, 15),
[F_VBUS_CHGPORT] = REG_FIELD(VBUS_UCD_STATUS, 12, 13),
[F_VBUS_PUPDET] = REG_FIELD(VBUS_UCD_STATUS, 11, 11),
[F_VBUS_VBUS_VLD] = REG_FIELD(VBUS_UCD_STATUS, 7, 7),
[F_VBUS_CHGDET] = REG_FIELD(VBUS_UCD_STATUS, 6, 6),
[F_VBUS_OTGDET] = REG_FIELD(VBUS_UCD_STATUS, 3, 3),
[F_VBUS_VBINOP] = REG_FIELD(VBUS_IDD_STATUS, 6, 6),
[F_VBUS_EXTID] = REG_FIELD(VBUS_IDD_STATUS, 5, 5),
[F_VBUS_IDRDET] = REG_FIELD(VBUS_IDD_STATUS, 4, 4),
[F_VBUS_INDO] = REG_FIELD(VBUS_IDD_STATUS, 0, 3),
[F_VBUS_UCDSWEN] = REG_FIELD(VCC_UCD_FCTRL_SET, 10, 10),
[F_VBUS_RREF_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 9, 9),
[F_VBUS_DPPU_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 8, 8),
[F_VBUS_DPREF_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 7, 7),
[F_VBUS_DMREF_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 6, 6),
[F_VBUS_DPDET_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 5, 5),
[F_VBUS_DMDET_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 4, 4),
[F_VBUS_DPSINK_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 3, 3),
[F_VBUS_DMSINK_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 2, 2),
[F_VBUS_DP_BUFF_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 1, 1),
[F_VBUS_DM_BUFF_EN] = REG_FIELD(VCC_UCD_FCTRL_SET, 0, 0),
[F_VBUS_EXTCLKENBL] = REG_FIELD(VBUS_UCD_FCTRL_EN, 15, 15),
[F_VBUS_PLSTESTEN] = REG_FIELD(VBUS_UCD_FCTRL_EN, 14, 14),
[F_VBUS_UCDSWEN_TSTENB] = REG_FIELD(VBUS_UCD_FCTRL_EN, 10, 10),
[F_VBUS_RREF_EN_TSTENB] = REG_FIELD(VBUS_UCD_FCTRL_EN, 9, 9),
[F_VBUS_DPPU_EN_TSTENB] = REG_FIELD(VBUS_UCD_FCTRL_EN, 8, 8),
[F_VBUS_DPREF_EN_TSTENB] = REG_FIELD(VBUS_UCD_FCTRL_EN, 7, 7),
[F_VBUS_DMREF_EN_TSTENB] = REG_FIELD(VBUS_UCD_FCTRL_EN, 6, 6),
[F_VBUS_DPDET_EN_TSTENB] = REG_FIELD(VBUS_UCD_FCTRL_EN, 5, 5),
[F_VBUS_DMDET_EN_TSTENB] = REG_FIELD(VBUS_UCD_FCTRL_EN, 4, 4),
[F_VBUS_DPSINK_EN_TSTENB] = REG_FIELD(VBUS_UCD_FCTRL_EN, 3, 3),
[F_VBUS_DMSINK_EN_TSTENB] = REG_FIELD(VBUS_UCD_FCTRL_EN, 2, 2),
[F_VBUS_DP_BUFF_EN_TSTENB] = REG_FIELD(VBUS_UCD_FCTRL_EN, 1, 1),
[F_VBUS_DM_BUFF_EN_TSTENB] = REG_FIELD(VBUS_UCD_FCTRL_EN, 0, 0),
[F_CHIP_ID] = REG_FIELD(CHIP_ID, 0, 15),
[F_CHIP_REV] = REG_FIELD(CHIP_REV, 0, 15),
[F_ONE_CELL_MODE] = REG_FIELD(IC_SET1, 11, 11),
[F_cell] = REG_FIELD(IC_SET1, 1, 1),
[F_VACP_AUTO_DISCHG] = REG_FIELD(IC_SET1, 9, 9),
[F_VACP_LOAD] = REG_FIELD(IC_SET1, 8, 8),
[F_ACOK_POL] = REG_FIELD(IC_SET1, 1, 1),
[F_ACOK_DISEN] = REG_FIELD(IC_SET1, 0, 0),
[F_DEBUG_SET1] = REG_FIELD(IC_SET2, 4, 8),
[F_DEBUG_SET0] = REG_FIELD(IC_SET2, 0, 0),
[F_MONRST_STATE] = REG_FIELD(SYSTEM_STATUS, 6, 6),
[F_ALMRST_STATE] = REG_FIELD(SYSTEM_STATUS, 5, 5),
[F_CHGRST_STATE] = REG_FIELD(SYSTEM_STATUS, 4, 4),
[F_OTPLD_STATE] = REG_FIELD(SYSTEM_STATUS, 1, 1),
[F_ALLRST_STATE] = REG_FIELD(SYSTEM_STATUS, 0, 0),
[F_PROTECT_SET] = REG_FIELD(PROTECT_SET, 0, 15),
[F_MAP_SET] = REG_FIELD(MAP_SET, 0, 15),
[F_ADCINTERVAL] = REG_FIELD(VM_CTRL_SET, 14, 15),
[F_ADCMOD] = REG_FIELD(VM_CTRL_SET, 12, 13),
[F_ADCTMOD] = REG_FIELD(VM_CTRL_SET, 10, 11),
[F_EXTIADPEN] = REG_FIELD(VM_CTRL_SET, 9, 9),
[F_VSYSENB] = REG_FIELD(VM_CTRL_SET, 8, 8),
[F_VCCENB] = REG_FIELD(VM_CTRL_SET, 7, 7),
[F_VBUSENB] = REG_FIELD(VM_CTRL_SET, 6, 6),
[F_VACPENB] = REG_FIELD(VM_CTRL_SET, 5, 5),
[F_IACPENB] = REG_FIELD(VM_CTRL_SET, 4, 4),
[F_THERMENB] = REG_FIELD(VM_CTRL_SET, 3, 3),
[F_VBATENB] = REG_FIELD(VM_CTRL_SET, 2, 2),
[F_IBATMENB] = REG_FIELD(VM_CTRL_SET, 1, 1),
[F_IBATPENB] = REG_FIELD(VM_CTRL_SET, 0, 0),
[F_TMPTHR1B] = REG_FIELD(THERM_WINDOW_SET1, 8, 15),
[F_TMPTHR1A] = REG_FIELD(THERM_WINDOW_SET1, 0, 7),
[F_TMPTHR2B] = REG_FIELD(THERM_WINDOW_SET2, 8, 15),
[F_TMPTHR2A] = REG_FIELD(THERM_WINDOW_SET2, 0, 7),
[F_TMPTHR3B] = REG_FIELD(THERM_WINDOW_SET3, 8, 15),
[F_TMPTHR3A] = REG_FIELD(THERM_WINDOW_SET3, 0, 7),
[F_TMPTHR4B] = REG_FIELD(THERM_WINDOW_SET4, 8, 15),
[F_TMPTHR4A] = REG_FIELD(THERM_WINDOW_SET4, 0, 7),
[F_TMPTHR5B] = REG_FIELD(THERM_WINDOW_SET5, 8, 15),
[F_TMPTHR5A] = REG_FIELD(THERM_WINDOW_SET5, 0, 7),
[F_IBATP_TH_SET] = REG_FIELD(IBATP_TH_SET, 0, 14),
[F_IBATM_TH_SET] = REG_FIELD(IBATM_TH_SET, 0, 14),
[F_VBAT_TH_SET] = REG_FIELD(VBAT_TH_SET, 0, 14),
[F_THERM_TH_SET] = REG_FIELD(THERM_TH_SET, 0, 7),
[F_IACP_TH_SET] = REG_FIELD(IACP_TH_SET, 0, 14),
[F_VACP_TH_SET] = REG_FIELD(VACP_TH_SET, 0, 14),
[F_VBUS_TH_SET] = REG_FIELD(VBUS_TH_SET, 0, 14),
[F_VCC_TH_SET] = REG_FIELD(VCC_TH_SET, 0, 14),
[F_VSYS_TH_SET] = REG_FIELD(VSYS_TH_SET, 0, 14),
[F_EXTIADP_TH_SET] = REG_FIELD(EXTIADP_TH_SET, 0, 11),
[F_IBATP_VAL] = REG_FIELD(IBATP_VAL, 0, 14),
[F_IBATP_AVE_VAL] = REG_FIELD(IBATP_AVE_VAL, 0, 14),
[F_IBATM_VAL] = REG_FIELD(IBATM_VAL, 0, 14),
[F_IBATM_AVE_VAL] = REG_FIELD(IBATM_AVE_VAL, 0, 14),
[F_VBAT_VAL] = REG_FIELD(VBAT_VAL, 0, 14),
[F_VBAT_AVE_VAL] = REG_FIELD(VBAT_AVE_VAL, 0, 14),
[F_THERM_VAL] = REG_FIELD(THERM_VAL, 0, 7),
[F_VTH_VAL] = REG_FIELD(VTH_VAL, 0, 11),
[F_IACP_VAL] = REG_FIELD(IACP_VAL, 0, 14),
[F_IACP_AVE_VAL] = REG_FIELD(IACP_AVE_VAL, 0, 14),
[F_VACP_VAL] = REG_FIELD(VACP_VAL, 0, 14),
[F_VACP_AVE_VAL] = REG_FIELD(VACP_AVE_VAL, 0, 14),
[F_VBUS_VAL] = REG_FIELD(VBUS_VAL, 0, 14),
[F_VBUS_AVE_VAL] = REG_FIELD(VBUS_AVE_VAL, 0, 14),
[F_VCC_VAL] = REG_FIELD(VCC_VAL, 0, 14),
[F_VCC_AVE_VAL] = REG_FIELD(VCC_AVE_VAL, 0, 14),
[F_VSYS_VAL] = REG_FIELD(VSYS_VAL, 0, 14),
[F_VSYS_AVE_VAL] = REG_FIELD(VSYS_AVE_VAL, 0, 14),
[F_EXTIADP_VAL] = REG_FIELD(EXTIADP_VAL, 0, 11),
[F_EXTIADP_AVE_VAL] = REG_FIELD(EXTIADP_AVE_VAL, 0, 11),
[F_VACPCLPS_TH_SET] = REG_FIELD(VACPCLPS_TH_SET, 7, 14),
[F_INT7_SET] = REG_FIELD(INT7_SET, 0, 15),
[F_INT6_SET] = REG_FIELD(INT6_SET, 0, 13),
[F_INT5_SET] = REG_FIELD(INT5_SET, 0, 13),
[F_INT4_SET] = REG_FIELD(INT4_SET, 0, 9),
[F_INT3_SET] = REG_FIELD(INT3_SET, 0, 15),
[F_INT2_SET] = REG_FIELD(INT2_SET, 0, 15),
[F_INT1_SET] = REG_FIELD(INT1_SET, 0, 15),
[F_INT0_SET] = REG_FIELD(INT0_SET, 0, 7),
[F_VBUS_RBUV_DET] = REG_FIELD(INT1_SET, 15, 15),
[F_VBUS_RBUV_RES] = REG_FIELD(INT1_SET, 14, 14),
[F_VBUS_TH_DET] = REG_FIELD(INT1_SET, 9, 9),
[F_VBUS_TH_RES] = REG_FIELD(INT1_SET, 8, 8),
[F_VBUS_IIN_MOD] = REG_FIELD(INT1_SET, 6, 6),
[F_VBUS_OV_DET] = REG_FIELD(INT1_SET, 5, 5),
[F_VBUS_OV_RES] = REG_FIELD(INT1_SET, 4, 4),
[F_VBUS_CLPS_DET] = REG_FIELD(INT1_SET, 3, 3),
[F_VBUS_CLPS] = REG_FIELD(INT1_SET, 2, 2),
[F_VBUS_DET] = REG_FIELD(INT1_SET, 1, 1),
[F_VBUS_RES] = REG_FIELD(INT1_SET, 0, 0),
[F_VCC_RBUV_DET] = REG_FIELD(INT2_SET, 15, 15),
[F_VCC_RBUV_RES] = REG_FIELD(INT2_SET, 14, 14),
[F_VCC_TH_DET] = REG_FIELD(INT2_SET, 9, 9),
[F_VCC_TH_RES] = REG_FIELD(INT2_SET, 8, 8),
[F_VCC_IIN_MOD] = REG_FIELD(INT2_SET, 6, 6),
[F_VCC_OVP_DET] = REG_FIELD(INT2_SET, 5, 5),
[F_VCC_OVP_RES] = REG_FIELD(INT2_SET, 4, 4),
[F_VCC_CLPS_DET] = REG_FIELD(INT2_SET, 3, 3),
[F_VCC_CLPS_RES] = REG_FIELD(INT2_SET, 2, 2),
[F_VCC_DET] = REG_FIELD(INT2_SET, 1, 1),
[F_VCC_RES] = REG_FIELD(INT2_SET, 0, 0),
[F_TH_DET] = REG_FIELD(INT3_SET, 15, 15),
[F_TH_RMV] = REG_FIELD(INT3_SET, 14, 14),
[F_TMP_OUT_DET] = REG_FIELD(INT3_SET, 11, 11),
[F_TMP_OUT_RES] = REG_FIELD(INT3_SET, 10, 10),
[F_VBAT_TH_DET] = REG_FIELD(INT3_SET, 9, 9),
[F_VBAT_TH_RES] = REG_FIELD(INT3_SET, 8, 8),
[F_IBAT_SHORT_DET] = REG_FIELD(INT3_SET, 7, 7),
[F_IBAT_SHORT_RES] = REG_FIELD(INT3_SET, 6, 6),
[F_VBAT_OV_DET] = REG_FIELD(INT3_SET, 5, 5),
[F_VBAT_OV_RES] = REG_FIELD(INT3_SET, 4, 4),
[F_BAT_ASSIST_DET] = REG_FIELD(INT3_SET, 3, 3),
[F_BAT_ASSIST_RES] = REG_FIELD(INT3_SET, 2, 2),
[F_VSYS_TH_DET] = REG_FIELD(INT4_SET, 9, 9),
[F_VSYS_TH_RES] = REG_FIELD(INT4_SET, 8, 8),
[F_VSYS_OV_DET] = REG_FIELD(INT4_SET, 5, 5),
[F_VSYS_OV_RES] = REG_FIELD(INT4_SET, 4, 4),
[F_VSYS_SHT_DET] = REG_FIELD(INT4_SET, 3, 3),
[F_VSYS_SHT_RES] = REG_FIELD(INT4_SET, 2, 2),
[F_VSYS_UV_DET] = REG_FIELD(INT4_SET, 1, 1),
[F_VSYS_UV_RES] = REG_FIELD(INT4_SET, 0, 0),
[F_OTP_LOAD_DONE] = REG_FIELD(INT5_SET, 13, 13),
[F_PWR_ON] = REG_FIELD(INT5_SET, 12, 12),
[F_EXTIADP_TRNS] = REG_FIELD(INT5_SET, 11, 11),
[F_EXTIADP_TH_DET] = REG_FIELD(INT5_SET, 9, 9),
[F_EXIADP_TH_RES] = REG_FIELD(INT5_SET, 8, 8),
[F_BAT_MNT_DET] = REG_FIELD(INT5_SET, 7, 7),
[F_BAT_MNT_RES] = REG_FIELD(INT5_SET, 6, 6),
[F_TSD_DET] = REG_FIELD(INT5_SET, 5, 5),
[F_TSD_RES] = REG_FIELD(INT5_SET, 4, 4),
[F_CHGWDT_EXP] = REG_FIELD(INT5_SET, 3, 3),
[F_THERMWDT_EXP] = REG_FIELD(INT5_SET, 2, 2),
[F_TMP_TRNS] = REG_FIELD(INT5_SET, 1, 1),
[F_CHG_TRNS] = REG_FIELD(INT5_SET, 0, 0),
[F_VBUS_UCD_PORT_DET] = REG_FIELD(INT6_SET, 13, 13),
[F_VBUS_UCD_UCHG_DET] = REG_FIELD(INT6_SET, 12, 12),
[F_VBUS_UCD_URID_RMV] = REG_FIELD(INT6_SET, 11, 11),
[F_VBUS_UCD_OTG_DET] = REG_FIELD(INT6_SET, 10, 10),
[F_VBUS_UCD_URID_MOD] = REG_FIELD(INT6_SET, 8, 8),
[F_VCC_UCD_PORT_DET] = REG_FIELD(INT6_SET, 5, 5),
[F_VCC_UCD_UCHG_DET] = REG_FIELD(INT6_SET, 4, 4),
[F_VCC_UCD_URID_RMV] = REG_FIELD(INT6_SET, 3, 3),
[F_VCC_UCD_OTG_DET] = REG_FIELD(INT6_SET, 2, 2),
[F_VCC_UCD_URID_MOD] = REG_FIELD(INT6_SET, 0, 0),
[F_PROCHOT_DET] = REG_FIELD(INT7_SET, 15, 15),
[F_PROCHOT_RES] = REG_FIELD(INT7_SET, 14, 14),
[F_VACP_DET] = REG_FIELD(INT7_SET, 11, 11),
[F_VACP_RES] = REG_FIELD(INT7_SET, 10, 10),
[F_VACP_TH_DET] = REG_FIELD(INT7_SET, 9, 9),
[F_VACP_TH_RES] = REG_FIELD(INT7_SET, 8, 8),
[F_IACP_TH_DET] = REG_FIELD(INT7_SET, 7, 7),
[F_IACP_THE_RES] = REG_FIELD(INT7_SET, 6, 6),
[F_THERM_TH_DET] = REG_FIELD(INT7_SET, 5, 5),
[F_THERM_TH_RES] = REG_FIELD(INT7_SET, 4, 4),
[F_IBATM_TH_DET] = REG_FIELD(INT7_SET, 3, 3),
[F_IBATM_TH_RES] = REG_FIELD(INT7_SET, 2, 2),
[F_IBATP_TH_DET] = REG_FIELD(INT7_SET, 1, 1),
[F_IBATP_TH_RES] = REG_FIELD(INT7_SET, 0, 0),
[F_INT7_STATUS] = REG_FIELD(INT7_STATUS, 0, 15),
[F_INT6_STATUS] = REG_FIELD(INT6_STATUS, 0, 13),
[F_INT5_STATUS] = REG_FIELD(INT5_STATUS, 0, 13),
[F_INT4_STATUS] = REG_FIELD(INT4_STATUS, 0, 9),
[F_INT3_STATUS] = REG_FIELD(INT3_STATUS, 0, 15),
[F_INT2_STATUS] = REG_FIELD(INT2_STATUS, 0, 15),
[F_INT1_STATUS] = REG_FIELD(INT1_STATUS, 0, 15),
[F_INT0_STATUS] = REG_FIELD(INT0_STATUS, 0, 7),
[F_ILIM_DECREASE] = REG_FIELD(OTPREG0, 0, 15),
[F_RESERVE_OTPREG1] = REG_FIELD(OTPREG1, 0, 15),
[F_POWER_SAVE_MODE] = REG_FIELD(SMBREG, 0, 15),
[F_DEBUG_MODE_SET] = REG_FIELD(DEBUG_MODE_SET, 0, 15),
[F_DEBUG0x14] = REG_FIELD(DEBUG0x14, 0, 15),
[F_DEBUG0x1A] = REG_FIELD(DEBUG0x1A, 0, 15),
};
/* CHGSTM_STATEs */
#define CHGSTM_SUSPEND 0x00
#define CHGSTM_TRICKLE_CHARGE 0x01
#define CHGSTM_PRE_CHARGE 0x02
#define CHGSTM_FAST_CHARGE 0x03
#define CHGSTM_TOP_OFF 0x04
#define CHGSTM_DONE 0x05
#define CHGSTM_OTG 0x08
#define CHGSTM_OTG_DONE 0x09
#define CHGSTM_TEMPERATURE_ERROR_1 0x10
#define CHGSTM_TEMPERATURE_ERROR_2 0x11
#define CHGSTM_TEMPERATURE_ERROR_3 0x12
#define CHGSTM_TEMPERATURE_ERROR_4 0x13
#define CHGSTM_TEMPERATURE_ERROR_5 0x14
#define CHGSTM_TEMPERATURE_ERROR_6 0x15
#define CHGSTM_TEMPERATURE_ERROR_7 0x18
#define CHGSTM_THERMAL_SHUT_DOWN_1 0x20
#define CHGSTM_THERMAL_SHUT_DOWN_2 0x21
#define CHGSTM_THERMAL_SHUT_DOWN_3 0x22
#define CHGSTM_THERMAL_SHUT_DOWN_4 0x23
#define CHGSTM_THERMAL_SHUT_DOWN_5 0x24
#define CHGSTM_THERMAL_SHUT_DOWN_6 0x25
#define CHGSTM_THERMAL_SHUT_DOWN_7 0x28
#define CHGSTM_BATTERY_ERROR 0x40
/* VBAT_VSYS_STATUS */
#define STATUS_VSYS_OV BIT(15)
#define STATUS_VSYS_SSD BIT(14)
#define STATUS_VSYS_SCP BIT(13)
#define STATUS_VSYS_UVN BIT(12)
#define STATUS_IBAT_SHORT BIT(6)
#define STATUS_VBAT_OV BIT(3)
#define STATUS_DEAD_BAT BIT(0)
/* VBUS_VCC_STATUS */
#define STATUS_VACP_DET BIT(12)
#define STATUS_VCC_OVP BIT(11)
#define STATUS_ILIM_VCC_MOD BIT(10)
#define STATUS_VCC_CLPS BIT(9)
#define STATUS_VCC_DET BIT(8)
#define STATUS_VBUS_OVP BIT(3)
#define STATUS_ILIM_VBUS_MOD BIT(2)
#define STATUS_VBUS_CLPS BIT(1)
#define STATUS_VBUS_DET BIT(0)
/* Interrupt set/status definitions */
/* INT 0 */
#define INT0_INT7_STATUS BIT(7)
#define INT0_INT6_STATUS BIT(6)
#define INT0_INT5_STATUS BIT(5)
#define INT0_INT4_STATUS BIT(4)
#define INT0_INT3_STATUS BIT(3)
#define INT0_INT2_STATUS BIT(2)
#define INT0_INT1_STATUS BIT(1)
#define INT0_INT0_STATUS BIT(0)
#define INT0_ALL 0xff
/* INT 1 */
#define VBUS_RBUV_DET BIT(15)
#define VBUS_RBUV_RES BIT(14)
#define VBUS_TH_DET BIT(9)
#define VBUS_TH_RES BIT(8)
#define VBUS_IIN_MOD BIT(6)
#define VBUS_OV_DET BIT(5)
#define VBUS_OV_RES BIT(4)
#define VBUS_CLPS_DET BIT(3)
#define VBUS_CLPS BIT(2)
#define VBUS_DET BIT(1)
#define VBUS_RES BIT(0)
#define INT1_ALL (VBUS_RBUV_DET|\
VBUS_RBUV_RES|\
VBUS_TH_DET |\
VBUS_TH_RES |\
VBUS_IIN_MOD|\
VBUS_OV_DET |\
VBUS_OV_RES |\
VBUS_CLPS_DET |\
VBUS_CLPS |\
VBUS_DET |\
VBUS_RES)
/* INT 2 */
#define VCC_RBUV_DET BIT(15)
#define VCC_RBUV_RES BIT(14)
#define VCC_TH_DET BIT(9)
#define VCC_TH_RES BIT(8)
#define VCC_IIN_MOD BIT(6)
#define VCC_OVP_DET BIT(5)
#define VCC_OVP_RES BIT(4)
#define VCC_CLPS_DET BIT(3)
#define VCC_CLPS_RES BIT(2)
#define VCC_DET BIT(1)
#define VCC_RES BIT(0)
#define INT2_ALL (VCC_RBUV_DET |\
VCC_RBUV_RES |\
VCC_TH_DET |\
VCC_TH_RES |\
VCC_IIN_MOD |\
VCC_OVP_DET |\
VCC_OVP_RES |\
VCC_CLPS_DET |\
VCC_CLPS_RES |\
VCC_DET |\
VCC_RES)
/* INT 3 */
#define TH_DET BIT(15)
#define TH_RMV BIT(14)
#define TMP_OUT_DET BIT(11)
#define TMP_OUT_RES BIT(10)
#define VBAT_TH_DET BIT(9)
#define VBAT_TH_RES BIT(8)
#define IBAT_SHORT_DET BIT(7)
#define IBAT_SHORT_RES BIT(6)
#define VBAT_OV_DET BIT(5)
#define VBAT_OV_RES BIT(4)
#define BAT_ASSIST_DET BIT(3)
#define BAT_ASSIST_RES BIT(2)
#define INT3_ALL (TH_DET |\
TH_RMV |\
TMP_OUT_DET |\
TMP_OUT_RES |\
VBAT_TH_DET |\
VBAT_TH_RES |\
IBAT_SHORT_DET |\
IBAT_SHORT_RES |\
VBAT_OV_DET |\
VBAT_OV_RES |\
BAT_ASSIST_DET |\
BAT_ASSIST_RES)
/* INT 4 */
#define VSYS_TH_DET BIT(9)
#define VSYS_TH_RES BIT(8)
#define VSYS_OV_DET BIT(5)
#define VSYS_OV_RES BIT(4)
#define VSYS_SHT_DET BIT(3)
#define VSYS_SHT_RES BIT(2)
#define VSYS_UV_DET BIT(1)
#define VSYS_UV_RES BIT(0)
#define INT4_ALL (VSYS_TH_DET |\
VSYS_TH_RES |\
VSYS_OV_DET |\
VSYS_OV_RES |\
VSYS_SHT_DET |\
VSYS_SHT_RES |\
VSYS_UV_DET |\
VSYS_UV_RES)
/* INT 5*/
#define OTP_LOAD_DONE BIT(13)
#define PWR_ON BIT(12)
#define EXTIADP_TRNS BIT(11)
#define EXTIADP_TH_DET BIT(9)
#define EXIADP_TH_RES BIT(8)
#define BAT_MNT_DET BIT(7)
#define BAT_MNT_RES BIT(6)
#define TSD_DET BIT(5)
#define TSD_RES BIT(4)
#define CHGWDT_EXP BIT(3)
#define THERMWDT_EXP BIT(2)
#define TMP_TRNS BIT(1)
#define CHG_TRNS BIT(0)
#define INT5_ALL (OTP_LOAD_DONE |\
PWR_ON |\
EXTIADP_TRNS |\
EXTIADP_TH_DET |\
EXIADP_TH_RES |\
BAT_MNT_DET |\
BAT_MNT_RES |\
TSD_DET |\
TSD_RES |\
CHGWDT_EXP |\
THERMWDT_EXP |\
TMP_TRNS |\
CHG_TRNS)
/* INT 6*/
#define VBUS_UCD_PORT_DET BIT(13)
#define VBUS_UCD_UCHG_DET BIT(12)
#define VBUS_UCD_URID_RMV BIT(11)
#define VBUS_UCD_OTG_DET BIT(10)
#define VBUS_UCD_URID_MOD BIT(8)
#define VCC_UCD_PORT_DET BIT(5)
#define VCC_UCD_UCHG_DET BIT(4)
#define VCC_UCD_URID_RMV BIT(3)
#define VCC_UCD_OTG_DET BIT(2)
#define VCC_UCD_URID_MOD BIT(0)
#define INT6_ALL (VBUS_UCD_PORT_DET |\
VBUS_UCD_UCHG_DET |\
VBUS_UCD_URID_RMV |\
VBUS_UCD_OTG_DET |\
VBUS_UCD_URID_MOD |\
VCC_UCD_PORT_DET |\
VCC_UCD_UCHG_DET |\
VCC_UCD_URID_RMV |\
VCC_UCD_OTG_DET |\
VCC_UCD_URID_MOD)
/* INT 7 */
#define PROCHOT_DET BIT(15)
#define PROCHOT_RES BIT(14)
#define VACP_DET BIT(11)
#define VACP_RES BIT(10)
#define VACP_TH_DET BIT(9)
#define VACP_TH_RES BIT(8)
#define IACP_TH_DET BIT(7)
#define IACP_THE_RES BIT(6)
#define THERM_TH_DET BIT(5)
#define THERM_TH_RES BIT(4)
#define IBATM_TH_DET BIT(3)
#define IBATM_TH_RES BIT(2)
#define IBATP_TH_DET BIT(1)
#define IBATP_TH_RES BIT(0)
#define INT7_ALL (PROCHOT_DET |\
PROCHOT_RES |\
VACP_DET |\
VACP_RES |\
VACP_TH_DET |\
VACP_TH_RES |\
IACP_TH_DET |\
IACP_THE_RES |\
THERM_TH_DET |\
THERM_TH_RES |\
IBATM_TH_DET |\
IBATM_TH_RES |\
IBATP_TH_DET |\
IBATP_TH_RES)
/* SYSTEM_CTRL_SET*/
#define MONRST BIT(6)
#define ALMRST BIT(5)
#define CHGRST BIT(4)
#define OTPLD BIT(1)
#define ALLRST BIT(0)
/* F_BATTEMP */
#define ROOM 0x0
#define HOT1 0x1
#define HOT2 0x2
#define HOT3 0x3
#define COLD1 0x4
#define COLD2 0x5
#define TEMP_DIS 0x6
#define BATT_OPEN 0x7
#endif
...@@ -673,7 +673,7 @@ static int bq24190_register_reset(struct bq24190_dev_info *bdi) ...@@ -673,7 +673,7 @@ static int bq24190_register_reset(struct bq24190_dev_info *bdi)
* { .type = "bq24190", .addr = 0x6b, .properties = pe, .irq = irq }; * { .type = "bq24190", .addr = 0x6b, .properties = pe, .irq = irq };
* struct i2c_adapter ad = { ... }; * struct i2c_adapter ad = { ... };
* i2c_add_adapter(&ad); * i2c_add_adapter(&ad);
* i2c_new_device(&ad, &bi); * i2c_new_client_device(&ad, &bi);
*/ */
if (device_property_read_bool(bdi->dev, "disable-reset")) if (device_property_read_bool(bdi->dev, "disable-reset"))
return 0; return 0;
......
...@@ -32,6 +32,13 @@ enum bq25890_chip_version { ...@@ -32,6 +32,13 @@ enum bq25890_chip_version {
BQ25896, BQ25896,
}; };
static const char *const bq25890_chip_name[] = {
"BQ25890",
"BQ25892",
"BQ25895",
"BQ25896",
};
enum bq25890_fields { enum bq25890_fields {
F_EN_HIZ, F_EN_ILIM, F_IILIM, /* Reg00 */ F_EN_HIZ, F_EN_ILIM, F_IILIM, /* Reg00 */
F_BHOT, F_BCOLD, F_VINDPM_OFS, /* Reg01 */ F_BHOT, F_BCOLD, F_VINDPM_OFS, /* Reg01 */
...@@ -119,6 +126,7 @@ static const struct regmap_access_table bq25890_writeable_regs = { ...@@ -119,6 +126,7 @@ static const struct regmap_access_table bq25890_writeable_regs = {
static const struct regmap_range bq25890_volatile_reg_ranges[] = { static const struct regmap_range bq25890_volatile_reg_ranges[] = {
regmap_reg_range(0x00, 0x00), regmap_reg_range(0x00, 0x00),
regmap_reg_range(0x02, 0x02),
regmap_reg_range(0x09, 0x09), regmap_reg_range(0x09, 0x09),
regmap_reg_range(0x0b, 0x14), regmap_reg_range(0x0b, 0x14),
}; };
...@@ -246,6 +254,7 @@ enum bq25890_table_ids { ...@@ -246,6 +254,7 @@ enum bq25890_table_ids {
/* range tables */ /* range tables */
TBL_ICHG, TBL_ICHG,
TBL_ITERM, TBL_ITERM,
TBL_IILIM,
TBL_VREG, TBL_VREG,
TBL_BOOSTV, TBL_BOOSTV,
TBL_SYSVMIN, TBL_SYSVMIN,
...@@ -286,6 +295,7 @@ static const union { ...@@ -286,6 +295,7 @@ static const union {
/* TODO: BQ25896 has max ICHG 3008 mA */ /* TODO: BQ25896 has max ICHG 3008 mA */
[TBL_ICHG] = { .rt = {0, 5056000, 64000} }, /* uA */ [TBL_ICHG] = { .rt = {0, 5056000, 64000} }, /* uA */
[TBL_ITERM] = { .rt = {64000, 1024000, 64000} }, /* uA */ [TBL_ITERM] = { .rt = {64000, 1024000, 64000} }, /* uA */
[TBL_IILIM] = { .rt = {50000, 3200000, 50000} }, /* uA */
[TBL_VREG] = { .rt = {3840000, 4608000, 16000} }, /* uV */ [TBL_VREG] = { .rt = {3840000, 4608000, 16000} }, /* uV */
[TBL_BOOSTV] = { .rt = {4550000, 5510000, 64000} }, /* uV */ [TBL_BOOSTV] = { .rt = {4550000, 5510000, 64000} }, /* uV */
[TBL_SYSVMIN] = { .rt = {3000000, 3700000, 100000} }, /* uV */ [TBL_SYSVMIN] = { .rt = {3000000, 3700000, 100000} }, /* uV */
...@@ -367,18 +377,42 @@ enum bq25890_chrg_fault { ...@@ -367,18 +377,42 @@ enum bq25890_chrg_fault {
CHRG_FAULT_TIMER_EXPIRED, CHRG_FAULT_TIMER_EXPIRED,
}; };
static bool bq25890_is_adc_property(enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
case POWER_SUPPLY_PROP_CURRENT_NOW:
return true;
default:
return false;
}
}
static irqreturn_t __bq25890_handle_irq(struct bq25890_device *bq);
static int bq25890_power_supply_get_property(struct power_supply *psy, static int bq25890_power_supply_get_property(struct power_supply *psy,
enum power_supply_property psp, enum power_supply_property psp,
union power_supply_propval *val) union power_supply_propval *val)
{ {
int ret;
struct bq25890_device *bq = power_supply_get_drvdata(psy); struct bq25890_device *bq = power_supply_get_drvdata(psy);
struct bq25890_state state; struct bq25890_state state;
bool do_adc_conv;
int ret;
mutex_lock(&bq->lock); mutex_lock(&bq->lock);
/* update state in case we lost an interrupt */
__bq25890_handle_irq(bq);
state = bq->state; state = bq->state;
do_adc_conv = !state.online && bq25890_is_adc_property(psp);
if (do_adc_conv)
bq25890_field_write(bq, F_CONV_START, 1);
mutex_unlock(&bq->lock); mutex_unlock(&bq->lock);
if (do_adc_conv)
regmap_field_read_poll_timeout(bq->rmap_fields[F_CONV_START],
ret, !ret, 25000, 1000000);
switch (psp) { switch (psp) {
case POWER_SUPPLY_PROP_STATUS: case POWER_SUPPLY_PROP_STATUS:
if (!state.online) if (!state.online)
...@@ -395,22 +429,24 @@ static int bq25890_power_supply_get_property(struct power_supply *psy, ...@@ -395,22 +429,24 @@ static int bq25890_power_supply_get_property(struct power_supply *psy,
break; break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
if (!state.online || state.chrg_status == STATUS_NOT_CHARGING ||
state.chrg_status == STATUS_TERMINATION_DONE)
val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
else if (state.chrg_status == STATUS_PRE_CHARGING)
val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
else if (state.chrg_status == STATUS_FAST_CHARGING)
val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
else /* unreachable */
val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
break;
case POWER_SUPPLY_PROP_MANUFACTURER: case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = BQ25890_MANUFACTURER; val->strval = BQ25890_MANUFACTURER;
break; break;
case POWER_SUPPLY_PROP_MODEL_NAME: case POWER_SUPPLY_PROP_MODEL_NAME:
if (bq->chip_version == BQ25890) val->strval = bq25890_chip_name[bq->chip_version];
val->strval = "BQ25890";
else if (bq->chip_version == BQ25892)
val->strval = "BQ25892";
else if (bq->chip_version == BQ25895)
val->strval = "BQ25895";
else if (bq->chip_version == BQ25896)
val->strval = "BQ25896";
else
val->strval = "UNKNOWN";
break; break;
case POWER_SUPPLY_PROP_ONLINE: case POWER_SUPPLY_PROP_ONLINE:
...@@ -430,15 +466,6 @@ static int bq25890_power_supply_get_property(struct power_supply *psy, ...@@ -430,15 +466,6 @@ static int bq25890_power_supply_get_property(struct power_supply *psy,
val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
break; break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
ret = bq25890_field_read(bq, F_ICHGR); /* read measured value */
if (ret < 0)
return ret;
/* converted_val = ADC_val * 50mA (table 10.3.19) */
val->intval = ret * 50000;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
val->intval = bq25890_find_val(bq->init_data.ichg, TBL_ICHG); val->intval = bq25890_find_val(bq->init_data.ichg, TBL_ICHG);
break; break;
...@@ -461,10 +488,22 @@ static int bq25890_power_supply_get_property(struct power_supply *psy, ...@@ -461,10 +488,22 @@ static int bq25890_power_supply_get_property(struct power_supply *psy,
val->intval = bq25890_find_val(bq->init_data.vreg, TBL_VREG); val->intval = bq25890_find_val(bq->init_data.vreg, TBL_VREG);
break; break;
case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
val->intval = bq25890_find_val(bq->init_data.iprechg, TBL_ITERM);
break;
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
val->intval = bq25890_find_val(bq->init_data.iterm, TBL_ITERM); val->intval = bq25890_find_val(bq->init_data.iterm, TBL_ITERM);
break; break;
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
ret = bq25890_field_read(bq, F_IILIM);
if (ret < 0)
return ret;
val->intval = bq25890_find_val(ret, TBL_IILIM);
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW: case POWER_SUPPLY_PROP_VOLTAGE_NOW:
ret = bq25890_field_read(bq, F_SYSV); /* read measured value */ ret = bq25890_field_read(bq, F_SYSV); /* read measured value */
if (ret < 0) if (ret < 0)
...@@ -474,6 +513,15 @@ static int bq25890_power_supply_get_property(struct power_supply *psy, ...@@ -474,6 +513,15 @@ static int bq25890_power_supply_get_property(struct power_supply *psy,
val->intval = 2304000 + ret * 20000; val->intval = 2304000 + ret * 20000;
break; break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
ret = bq25890_field_read(bq, F_ICHGR); /* read measured value */
if (ret < 0)
return ret;
/* converted_val = ADC_val * 50mA (table 10.3.19) */
val->intval = ret * -50000;
break;
default: default:
return -EINVAL; return -EINVAL;
} }
...@@ -513,74 +561,50 @@ static int bq25890_get_chip_state(struct bq25890_device *bq, ...@@ -513,74 +561,50 @@ static int bq25890_get_chip_state(struct bq25890_device *bq,
return 0; return 0;
} }
static bool bq25890_state_changed(struct bq25890_device *bq, static irqreturn_t __bq25890_handle_irq(struct bq25890_device *bq)
struct bq25890_state *new_state)
{
struct bq25890_state old_state;
mutex_lock(&bq->lock);
old_state = bq->state;
mutex_unlock(&bq->lock);
return (old_state.chrg_status != new_state->chrg_status ||
old_state.chrg_fault != new_state->chrg_fault ||
old_state.online != new_state->online ||
old_state.bat_fault != new_state->bat_fault ||
old_state.boost_fault != new_state->boost_fault ||
old_state.vsys_status != new_state->vsys_status);
}
static void bq25890_handle_state_change(struct bq25890_device *bq,
struct bq25890_state *new_state)
{ {
struct bq25890_state new_state;
int ret; int ret;
struct bq25890_state old_state;
mutex_lock(&bq->lock); ret = bq25890_get_chip_state(bq, &new_state);
old_state = bq->state; if (ret < 0)
mutex_unlock(&bq->lock); return IRQ_NONE;
if (!memcmp(&bq->state, &new_state, sizeof(new_state)))
return IRQ_NONE;
if (!new_state->online) { /* power removed */ if (!new_state.online && bq->state.online) { /* power removed */
/* disable ADC */ /* disable ADC */
ret = bq25890_field_write(bq, F_CONV_START, 0); ret = bq25890_field_write(bq, F_CONV_START, 0);
if (ret < 0) if (ret < 0)
goto error; goto error;
} else if (!old_state.online) { /* power inserted */ } else if (new_state.online && !bq->state.online) { /* power inserted */
/* enable ADC, to have control of charge current/voltage */ /* enable ADC, to have control of charge current/voltage */
ret = bq25890_field_write(bq, F_CONV_START, 1); ret = bq25890_field_write(bq, F_CONV_START, 1);
if (ret < 0) if (ret < 0)
goto error; goto error;
} }
return; bq->state = new_state;
power_supply_changed(bq->charger);
return IRQ_HANDLED;
error: error:
dev_err(bq->dev, "Error communicating with the chip.\n"); dev_err(bq->dev, "Error communicating with the chip: %pe\n",
ERR_PTR(ret));
return IRQ_HANDLED;
} }
static irqreturn_t bq25890_irq_handler_thread(int irq, void *private) static irqreturn_t bq25890_irq_handler_thread(int irq, void *private)
{ {
struct bq25890_device *bq = private; struct bq25890_device *bq = private;
int ret; irqreturn_t ret;
struct bq25890_state state;
ret = bq25890_get_chip_state(bq, &state);
if (ret < 0)
goto handled;
if (!bq25890_state_changed(bq, &state))
goto handled;
bq25890_handle_state_change(bq, &state);
mutex_lock(&bq->lock); mutex_lock(&bq->lock);
bq->state = state; ret = __bq25890_handle_irq(bq);
mutex_unlock(&bq->lock); mutex_unlock(&bq->lock);
power_supply_changed(bq->charger); return ret;
handled:
return IRQ_HANDLED;
} }
static int bq25890_chip_reset(struct bq25890_device *bq) static int bq25890_chip_reset(struct bq25890_device *bq)
...@@ -610,7 +634,6 @@ static int bq25890_hw_init(struct bq25890_device *bq) ...@@ -610,7 +634,6 @@ static int bq25890_hw_init(struct bq25890_device *bq)
{ {
int ret; int ret;
int i; int i;
struct bq25890_state state;
const struct { const struct {
enum bq25890_fields id; enum bq25890_fields id;
...@@ -651,38 +674,37 @@ static int bq25890_hw_init(struct bq25890_device *bq) ...@@ -651,38 +674,37 @@ static int bq25890_hw_init(struct bq25890_device *bq)
} }
} }
/* Configure ADC for continuous conversions. This does not enable it. */ /* Configure ADC for continuous conversions when charging */
ret = bq25890_field_write(bq, F_CONV_RATE, 1); ret = bq25890_field_write(bq, F_CONV_RATE, !!bq->state.online);
if (ret < 0) { if (ret < 0) {
dev_dbg(bq->dev, "Config ADC failed %d\n", ret); dev_dbg(bq->dev, "Config ADC failed %d\n", ret);
return ret; return ret;
} }
ret = bq25890_get_chip_state(bq, &state); ret = bq25890_get_chip_state(bq, &bq->state);
if (ret < 0) { if (ret < 0) {
dev_dbg(bq->dev, "Get state failed %d\n", ret); dev_dbg(bq->dev, "Get state failed %d\n", ret);
return ret; return ret;
} }
mutex_lock(&bq->lock);
bq->state = state;
mutex_unlock(&bq->lock);
return 0; return 0;
} }
static enum power_supply_property bq25890_power_supply_props[] = { static const enum power_supply_property bq25890_power_supply_props[] = {
POWER_SUPPLY_PROP_MANUFACTURER, POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
}; };
static char *bq25890_charger_supplied_to[] = { static char *bq25890_charger_supplied_to[] = {
...@@ -881,17 +903,11 @@ static int bq25890_fw_probe(struct bq25890_device *bq) ...@@ -881,17 +903,11 @@ static int bq25890_fw_probe(struct bq25890_device *bq)
static int bq25890_probe(struct i2c_client *client, static int bq25890_probe(struct i2c_client *client,
const struct i2c_device_id *id) const struct i2c_device_id *id)
{ {
struct i2c_adapter *adapter = client->adapter;
struct device *dev = &client->dev; struct device *dev = &client->dev;
struct bq25890_device *bq; struct bq25890_device *bq;
int ret; int ret;
int i; int i;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(dev, "No support for SMBUS_BYTE_DATA\n");
return -ENODEV;
}
bq = devm_kzalloc(dev, sizeof(*bq), GFP_KERNEL); bq = devm_kzalloc(dev, sizeof(*bq), GFP_KERNEL);
if (!bq) if (!bq)
return -ENOMEM; return -ENOMEM;
...@@ -1004,34 +1020,34 @@ static int bq25890_suspend(struct device *dev) ...@@ -1004,34 +1020,34 @@ static int bq25890_suspend(struct device *dev)
* If charger is removed, while in suspend, make sure ADC is diabled * If charger is removed, while in suspend, make sure ADC is diabled
* since it consumes slightly more power. * since it consumes slightly more power.
*/ */
return bq25890_field_write(bq, F_CONV_START, 0); return bq25890_field_write(bq, F_CONV_RATE, 0);
} }
static int bq25890_resume(struct device *dev) static int bq25890_resume(struct device *dev)
{ {
int ret; int ret;
struct bq25890_state state;
struct bq25890_device *bq = dev_get_drvdata(dev); struct bq25890_device *bq = dev_get_drvdata(dev);
ret = bq25890_get_chip_state(bq, &state);
if (ret < 0)
return ret;
mutex_lock(&bq->lock); mutex_lock(&bq->lock);
bq->state = state;
mutex_unlock(&bq->lock); ret = bq25890_get_chip_state(bq, &bq->state);
if (ret < 0)
goto unlock;
/* Re-enable ADC only if charger is plugged in. */ /* Re-enable ADC only if charger is plugged in. */
if (state.online) { if (bq->state.online) {
ret = bq25890_field_write(bq, F_CONV_START, 1); ret = bq25890_field_write(bq, F_CONV_RATE, 1);
if (ret < 0) if (ret < 0)
return ret; goto unlock;
} }
/* signal userspace, maybe state changed while suspended */ /* signal userspace, maybe state changed while suspended */
power_supply_changed(bq->charger); power_supply_changed(bq->charger);
return 0; unlock:
mutex_unlock(&bq->lock);
return ret;
} }
#endif #endif
......
...@@ -1422,7 +1422,9 @@ static int charger_manager_prepare_sysfs(struct charger_manager *cm) ...@@ -1422,7 +1422,9 @@ static int charger_manager_prepare_sysfs(struct charger_manager *cm)
} }
static int cm_init_thermal_data(struct charger_manager *cm, static int cm_init_thermal_data(struct charger_manager *cm,
struct power_supply *fuel_gauge) struct power_supply *fuel_gauge,
enum power_supply_property *properties,
size_t *num_properties)
{ {
struct charger_desc *desc = cm->desc; struct charger_desc *desc = cm->desc;
union power_supply_propval val; union power_supply_propval val;
...@@ -1433,9 +1435,8 @@ static int cm_init_thermal_data(struct charger_manager *cm, ...@@ -1433,9 +1435,8 @@ static int cm_init_thermal_data(struct charger_manager *cm,
POWER_SUPPLY_PROP_TEMP, &val); POWER_SUPPLY_PROP_TEMP, &val);
if (!ret) { if (!ret) {
cm->charger_psy_desc.properties[cm->charger_psy_desc.num_properties] = properties[*num_properties] = POWER_SUPPLY_PROP_TEMP;
POWER_SUPPLY_PROP_TEMP; (*num_properties)++;
cm->charger_psy_desc.num_properties++;
cm->desc->measure_battery_temp = true; cm->desc->measure_battery_temp = true;
} }
#ifdef CONFIG_THERMAL #ifdef CONFIG_THERMAL
...@@ -1446,9 +1447,8 @@ static int cm_init_thermal_data(struct charger_manager *cm, ...@@ -1446,9 +1447,8 @@ static int cm_init_thermal_data(struct charger_manager *cm,
return PTR_ERR(cm->tzd_batt); return PTR_ERR(cm->tzd_batt);
/* Use external thermometer */ /* Use external thermometer */
cm->charger_psy_desc.properties[cm->charger_psy_desc.num_properties] = properties[*num_properties] = POWER_SUPPLY_PROP_TEMP_AMBIENT;
POWER_SUPPLY_PROP_TEMP_AMBIENT; (*num_properties)++;
cm->charger_psy_desc.num_properties++;
cm->desc->measure_battery_temp = true; cm->desc->measure_battery_temp = true;
ret = 0; ret = 0;
} }
...@@ -1621,6 +1621,8 @@ static int charger_manager_probe(struct platform_device *pdev) ...@@ -1621,6 +1621,8 @@ static int charger_manager_probe(struct platform_device *pdev)
int j = 0; int j = 0;
union power_supply_propval val; union power_supply_propval val;
struct power_supply *fuel_gauge; struct power_supply *fuel_gauge;
enum power_supply_property *properties;
size_t num_properties;
struct power_supply_config psy_cfg = {}; struct power_supply_config psy_cfg = {};
if (IS_ERR(desc)) { if (IS_ERR(desc)) {
...@@ -1717,18 +1719,17 @@ static int charger_manager_probe(struct platform_device *pdev) ...@@ -1717,18 +1719,17 @@ static int charger_manager_probe(struct platform_device *pdev)
cm->charger_psy_desc.name = cm->psy_name_buf; cm->charger_psy_desc.name = cm->psy_name_buf;
/* Allocate for psy properties because they may vary */ /* Allocate for psy properties because they may vary */
cm->charger_psy_desc.properties = properties = devm_kcalloc(&pdev->dev,
devm_kcalloc(&pdev->dev,
ARRAY_SIZE(default_charger_props) + ARRAY_SIZE(default_charger_props) +
NUM_CHARGER_PSY_OPTIONAL, NUM_CHARGER_PSY_OPTIONAL,
sizeof(enum power_supply_property), GFP_KERNEL); sizeof(*properties), GFP_KERNEL);
if (!cm->charger_psy_desc.properties) if (!properties)
return -ENOMEM; return -ENOMEM;
memcpy(cm->charger_psy_desc.properties, default_charger_props, memcpy(properties, default_charger_props,
sizeof(enum power_supply_property) * sizeof(enum power_supply_property) *
ARRAY_SIZE(default_charger_props)); ARRAY_SIZE(default_charger_props));
cm->charger_psy_desc.num_properties = psy_default.num_properties; num_properties = ARRAY_SIZE(default_charger_props);
/* Find which optional psy-properties are available */ /* Find which optional psy-properties are available */
fuel_gauge = power_supply_get_by_name(desc->psy_fuel_gauge); fuel_gauge = power_supply_get_by_name(desc->psy_fuel_gauge);
...@@ -1739,25 +1740,28 @@ static int charger_manager_probe(struct platform_device *pdev) ...@@ -1739,25 +1740,28 @@ static int charger_manager_probe(struct platform_device *pdev)
} }
if (!power_supply_get_property(fuel_gauge, if (!power_supply_get_property(fuel_gauge,
POWER_SUPPLY_PROP_CHARGE_NOW, &val)) { POWER_SUPPLY_PROP_CHARGE_NOW, &val)) {
cm->charger_psy_desc.properties[cm->charger_psy_desc.num_properties] = properties[num_properties] =
POWER_SUPPLY_PROP_CHARGE_NOW; POWER_SUPPLY_PROP_CHARGE_NOW;
cm->charger_psy_desc.num_properties++; num_properties++;
} }
if (!power_supply_get_property(fuel_gauge, if (!power_supply_get_property(fuel_gauge,
POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CURRENT_NOW,
&val)) { &val)) {
cm->charger_psy_desc.properties[cm->charger_psy_desc.num_properties] = properties[num_properties] =
POWER_SUPPLY_PROP_CURRENT_NOW; POWER_SUPPLY_PROP_CURRENT_NOW;
cm->charger_psy_desc.num_properties++; num_properties++;
} }
ret = cm_init_thermal_data(cm, fuel_gauge); ret = cm_init_thermal_data(cm, fuel_gauge, properties, &num_properties);
if (ret) { if (ret) {
dev_err(&pdev->dev, "Failed to initialize thermal data\n"); dev_err(&pdev->dev, "Failed to initialize thermal data\n");
cm->desc->measure_battery_temp = false; cm->desc->measure_battery_temp = false;
} }
power_supply_put(fuel_gauge); power_supply_put(fuel_gauge);
cm->charger_psy_desc.properties = properties;
cm->charger_psy_desc.num_properties = num_properties;
INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk); INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk);
/* Register sysfs entry for charger(regulator) */ /* Register sysfs entry for charger(regulator) */
......
// SPDX-License-Identifier: GPL-2.0
/*
* Fuel gauge driver for CellWise 2013 / 2015
*
* Copyright (C) 2012, RockChip
* Copyright (C) 2020, Tobias Schramm
*
* Authors: xuhuicong <xhc@rock-chips.com>
* Authors: Tobias Schramm <t.schramm@manjaro.org>
*/
#include <linux/bits.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/power_supply.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/time.h>
#include <linux/workqueue.h>
#define CW2015_SIZE_BATINFO 64
#define CW2015_RESET_TRIES 5
#define CW2015_REG_VERSION 0x00
#define CW2015_REG_VCELL 0x02
#define CW2015_REG_SOC 0x04
#define CW2015_REG_RRT_ALERT 0x06
#define CW2015_REG_CONFIG 0x08
#define CW2015_REG_MODE 0x0A
#define CW2015_REG_BATINFO 0x10
#define CW2015_MODE_SLEEP_MASK GENMASK(7, 6)
#define CW2015_MODE_SLEEP (0x03 << 6)
#define CW2015_MODE_NORMAL (0x00 << 6)
#define CW2015_MODE_QUICK_START (0x03 << 4)
#define CW2015_MODE_RESTART (0x0f << 0)
#define CW2015_CONFIG_UPDATE_FLG (0x01 << 1)
#define CW2015_ATHD(x) ((x) << 3)
#define CW2015_MASK_ATHD GENMASK(7, 3)
#define CW2015_MASK_SOC GENMASK(12, 0)
/* reset gauge of no valid state of charge could be polled for 40s */
#define CW2015_BAT_SOC_ERROR_MS (40 * MSEC_PER_SEC)
/* reset gauge if state of charge stuck for half an hour during charging */
#define CW2015_BAT_CHARGING_STUCK_MS (1800 * MSEC_PER_SEC)
/* poll interval from CellWise GPL Android driver example */
#define CW2015_DEFAULT_POLL_INTERVAL_MS 8000
#define CW2015_AVERAGING_SAMPLES 3
struct cw_battery {
struct device *dev;
struct workqueue_struct *battery_workqueue;
struct delayed_work battery_delay_work;
struct regmap *regmap;
struct power_supply *rk_bat;
struct power_supply_battery_info battery;
u8 *bat_profile;
bool charger_attached;
bool battery_changed;
int soc;
int voltage_mv;
int status;
int time_to_empty;
int charge_count;
u32 poll_interval_ms;
u8 alert_level;
unsigned int read_errors;
unsigned int charge_stuck_cnt;
};
static int cw_read_word(struct cw_battery *cw_bat, u8 reg, u16 *val)
{
__be16 value;
int ret;
ret = regmap_bulk_read(cw_bat->regmap, reg, &value, sizeof(value));
if (ret)
return ret;
*val = be16_to_cpu(value);
return 0;
}
static int cw_update_profile(struct cw_battery *cw_bat)
{
int ret;
unsigned int reg_val;
u8 reset_val;
/* make sure gauge is not in sleep mode */
ret = regmap_read(cw_bat->regmap, CW2015_REG_MODE, &reg_val);
if (ret)
return ret;
reset_val = reg_val;
if ((reg_val & CW2015_MODE_SLEEP_MASK) == CW2015_MODE_SLEEP) {
dev_err(cw_bat->dev,
"Gauge is in sleep mode, can't update battery info\n");
return -EINVAL;
}
/* write new battery info */
ret = regmap_raw_write(cw_bat->regmap, CW2015_REG_BATINFO,
cw_bat->bat_profile,
CW2015_SIZE_BATINFO);
if (ret)
return ret;
/* set config update flag */
reg_val |= CW2015_CONFIG_UPDATE_FLG;
reg_val &= ~CW2015_MASK_ATHD;
reg_val |= CW2015_ATHD(cw_bat->alert_level);
ret = regmap_write(cw_bat->regmap, CW2015_REG_CONFIG, reg_val);
if (ret)
return ret;
/* reset gauge to apply new battery profile */
reset_val &= ~CW2015_MODE_RESTART;
reg_val = reset_val | CW2015_MODE_RESTART;
ret = regmap_write(cw_bat->regmap, CW2015_REG_MODE, reg_val);
if (ret)
return ret;
/* wait for gauge to reset */
msleep(20);
/* clear reset flag */
ret = regmap_write(cw_bat->regmap, CW2015_REG_MODE, reset_val);
if (ret)
return ret;
/* wait for gauge to become ready */
ret = regmap_read_poll_timeout(cw_bat->regmap, CW2015_REG_SOC,
reg_val, reg_val <= 100,
10 * USEC_PER_MSEC, 10 * USEC_PER_SEC);
if (ret)
dev_err(cw_bat->dev,
"Gauge did not become ready after profile upload\n");
else
dev_dbg(cw_bat->dev, "Battery profile updated\n");
return ret;
}
static int cw_init(struct cw_battery *cw_bat)
{
int ret;
unsigned int reg_val = CW2015_MODE_SLEEP;
if ((reg_val & CW2015_MODE_SLEEP_MASK) == CW2015_MODE_SLEEP) {
reg_val = CW2015_MODE_NORMAL;
ret = regmap_write(cw_bat->regmap, CW2015_REG_MODE, reg_val);
if (ret)
return ret;
}
ret = regmap_read(cw_bat->regmap, CW2015_REG_CONFIG, &reg_val);
if (ret)
return ret;
if ((reg_val & CW2015_MASK_ATHD) != CW2015_ATHD(cw_bat->alert_level)) {
dev_dbg(cw_bat->dev, "Setting new alert level\n");
reg_val &= ~CW2015_MASK_ATHD;
reg_val |= ~CW2015_ATHD(cw_bat->alert_level);
ret = regmap_write(cw_bat->regmap, CW2015_REG_CONFIG, reg_val);
if (ret)
return ret;
}
ret = regmap_read(cw_bat->regmap, CW2015_REG_CONFIG, &reg_val);
if (ret)
return ret;
if (!(reg_val & CW2015_CONFIG_UPDATE_FLG)) {
dev_dbg(cw_bat->dev,
"Battery profile not present, uploading battery profile\n");
if (cw_bat->bat_profile) {
ret = cw_update_profile(cw_bat);
if (ret) {
dev_err(cw_bat->dev,
"Failed to upload battery profile\n");
return ret;
}
} else {
dev_warn(cw_bat->dev,
"No profile specified, continuing without profile\n");
}
} else if (cw_bat->bat_profile) {
u8 bat_info[CW2015_SIZE_BATINFO];
ret = regmap_raw_read(cw_bat->regmap, CW2015_REG_BATINFO,
bat_info, CW2015_SIZE_BATINFO);
if (ret) {
dev_err(cw_bat->dev,
"Failed to read stored battery profile\n");
return ret;
}
if (memcmp(bat_info, cw_bat->bat_profile, CW2015_SIZE_BATINFO)) {
dev_warn(cw_bat->dev, "Replacing stored battery profile\n");
ret = cw_update_profile(cw_bat);
if (ret)
return ret;
}
} else {
dev_warn(cw_bat->dev,
"Can't check current battery profile, no profile provided\n");
}
dev_dbg(cw_bat->dev, "Battery profile configured\n");
return 0;
}
static int cw_power_on_reset(struct cw_battery *cw_bat)
{
int ret;
unsigned char reset_val;
reset_val = CW2015_MODE_SLEEP;
ret = regmap_write(cw_bat->regmap, CW2015_REG_MODE, reset_val);
if (ret)
return ret;
/* wait for gauge to enter sleep */
msleep(20);
reset_val = CW2015_MODE_NORMAL;
ret = regmap_write(cw_bat->regmap, CW2015_REG_MODE, reset_val);
if (ret)
return ret;
ret = cw_init(cw_bat);
if (ret)
return ret;
return 0;
}
#define HYSTERESIS(current, previous, up, down) \
(((current) < (previous) + (up)) && ((current) > (previous) - (down)))
static int cw_get_soc(struct cw_battery *cw_bat)
{
unsigned int soc;
int ret;
ret = regmap_read(cw_bat->regmap, CW2015_REG_SOC, &soc);
if (ret)
return ret;
if (soc > 100) {
int max_error_cycles =
CW2015_BAT_SOC_ERROR_MS / cw_bat->poll_interval_ms;
dev_err(cw_bat->dev, "Invalid SoC %d%%\n", soc);
cw_bat->read_errors++;
if (cw_bat->read_errors > max_error_cycles) {
dev_warn(cw_bat->dev,
"Too many invalid SoC reports, resetting gauge\n");
cw_power_on_reset(cw_bat);
cw_bat->read_errors = 0;
}
return cw_bat->soc;
}
cw_bat->read_errors = 0;
/* Reset gauge if stuck while charging */
if (cw_bat->status == POWER_SUPPLY_STATUS_CHARGING && soc == cw_bat->soc) {
int max_stuck_cycles =
CW2015_BAT_CHARGING_STUCK_MS / cw_bat->poll_interval_ms;
cw_bat->charge_stuck_cnt++;
if (cw_bat->charge_stuck_cnt > max_stuck_cycles) {
dev_warn(cw_bat->dev,
"SoC stuck @%u%%, resetting gauge\n", soc);
cw_power_on_reset(cw_bat);
cw_bat->charge_stuck_cnt = 0;
}
} else {
cw_bat->charge_stuck_cnt = 0;
}
/* Ignore voltage dips during charge */
if (cw_bat->charger_attached && HYSTERESIS(soc, cw_bat->soc, 0, 3))
soc = cw_bat->soc;
/* Ignore voltage spikes during discharge */
if (!cw_bat->charger_attached && HYSTERESIS(soc, cw_bat->soc, 3, 0))
soc = cw_bat->soc;
return soc;
}
static int cw_get_voltage(struct cw_battery *cw_bat)
{
int ret, i, voltage_mv;
u16 reg_val;
u32 avg = 0;
for (i = 0; i < CW2015_AVERAGING_SAMPLES; i++) {
ret = cw_read_word(cw_bat, CW2015_REG_VCELL, &reg_val);
if (ret)
return ret;
avg += reg_val;
}
avg /= CW2015_AVERAGING_SAMPLES;
/*
* 305 uV per ADC step
* Use 312 / 1024 as efficient approximation of 305 / 1000
* Negligible error of 0.1%
*/
voltage_mv = avg * 312 / 1024;
dev_dbg(cw_bat->dev, "Read voltage: %d mV, raw=0x%04x\n",
voltage_mv, reg_val);
return voltage_mv;
}
static int cw_get_time_to_empty(struct cw_battery *cw_bat)
{
int ret;
u16 value16;
ret = cw_read_word(cw_bat, CW2015_REG_RRT_ALERT, &value16);
if (ret)
return ret;
return value16 & CW2015_MASK_SOC;
}
static void cw_update_charge_status(struct cw_battery *cw_bat)
{
int ret;
ret = power_supply_am_i_supplied(cw_bat->rk_bat);
if (ret < 0) {
dev_warn(cw_bat->dev, "Failed to get supply state: %d\n", ret);
} else {
bool charger_attached;
charger_attached = !!ret;
if (cw_bat->charger_attached != charger_attached) {
cw_bat->battery_changed = true;
if (charger_attached)
cw_bat->charge_count++;
}
cw_bat->charger_attached = charger_attached;
}
}
static void cw_update_soc(struct cw_battery *cw_bat)
{
int soc;
soc = cw_get_soc(cw_bat);
if (soc < 0)
dev_err(cw_bat->dev, "Failed to get SoC from gauge: %d\n", soc);
else if (cw_bat->soc != soc) {
cw_bat->soc = soc;
cw_bat->battery_changed = true;
}
}
static void cw_update_voltage(struct cw_battery *cw_bat)
{
int voltage_mv;
voltage_mv = cw_get_voltage(cw_bat);
if (voltage_mv < 0)
dev_err(cw_bat->dev, "Failed to get voltage from gauge: %d\n",
voltage_mv);
else
cw_bat->voltage_mv = voltage_mv;
}
static void cw_update_status(struct cw_battery *cw_bat)
{
int status = POWER_SUPPLY_STATUS_DISCHARGING;
if (cw_bat->charger_attached) {
if (cw_bat->soc >= 100)
status = POWER_SUPPLY_STATUS_FULL;
else
status = POWER_SUPPLY_STATUS_CHARGING;
}
if (cw_bat->status != status)
cw_bat->battery_changed = true;
cw_bat->status = status;
}
static void cw_update_time_to_empty(struct cw_battery *cw_bat)
{
int time_to_empty;
time_to_empty = cw_get_time_to_empty(cw_bat);
if (time_to_empty < 0)
dev_err(cw_bat->dev, "Failed to get time to empty from gauge: %d\n",
time_to_empty);
else if (cw_bat->time_to_empty != time_to_empty) {
cw_bat->time_to_empty = time_to_empty;
cw_bat->battery_changed = true;
}
}
static void cw_bat_work(struct work_struct *work)
{
struct delayed_work *delay_work;
struct cw_battery *cw_bat;
int ret;
unsigned int reg_val;
delay_work = to_delayed_work(work);
cw_bat = container_of(delay_work, struct cw_battery, battery_delay_work);
ret = regmap_read(cw_bat->regmap, CW2015_REG_MODE, &reg_val);
if (ret) {
dev_err(cw_bat->dev, "Failed to read mode from gauge: %d\n", ret);
} else {
if ((reg_val & CW2015_MODE_SLEEP_MASK) == CW2015_MODE_SLEEP) {
int i;
for (i = 0; i < CW2015_RESET_TRIES; i++) {
if (!cw_power_on_reset(cw_bat))
break;
}
}
cw_update_soc(cw_bat);
cw_update_voltage(cw_bat);
cw_update_charge_status(cw_bat);
cw_update_status(cw_bat);
cw_update_time_to_empty(cw_bat);
}
dev_dbg(cw_bat->dev, "charger_attached = %d\n", cw_bat->charger_attached);
dev_dbg(cw_bat->dev, "status = %d\n", cw_bat->status);
dev_dbg(cw_bat->dev, "soc = %d%%\n", cw_bat->soc);
dev_dbg(cw_bat->dev, "voltage = %dmV\n", cw_bat->voltage_mv);
if (cw_bat->battery_changed)
power_supply_changed(cw_bat->rk_bat);
cw_bat->battery_changed = false;
queue_delayed_work(cw_bat->battery_workqueue,
&cw_bat->battery_delay_work,
msecs_to_jiffies(cw_bat->poll_interval_ms));
}
static bool cw_battery_valid_time_to_empty(struct cw_battery *cw_bat)
{
return cw_bat->time_to_empty > 0 &&
cw_bat->time_to_empty < CW2015_MASK_SOC &&
cw_bat->status == POWER_SUPPLY_STATUS_DISCHARGING;
}
static int cw_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct cw_battery *cw_bat;
cw_bat = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = cw_bat->soc;
break;
case POWER_SUPPLY_PROP_STATUS:
val->intval = cw_bat->status;
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = !!cw_bat->voltage_mv;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = cw_bat->voltage_mv * 1000;
break;
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
if (cw_battery_valid_time_to_empty(cw_bat))
val->intval = cw_bat->time_to_empty;
else
val->intval = 0;
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
break;
case POWER_SUPPLY_PROP_CHARGE_COUNTER:
val->intval = cw_bat->charge_count;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
if (cw_bat->battery.charge_full_design_uah > 0)
val->intval = cw_bat->battery.charge_full_design_uah;
else
val->intval = 0;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
if (cw_battery_valid_time_to_empty(cw_bat) &&
cw_bat->battery.charge_full_design_uah > 0) {
/* calculate remaining capacity */
val->intval = cw_bat->battery.charge_full_design_uah;
val->intval = val->intval * cw_bat->soc / 100;
/* estimate current based on time to empty */
val->intval = 60 * val->intval / cw_bat->time_to_empty;
} else {
val->intval = 0;
}
break;
default:
break;
}
return 0;
}
static enum power_supply_property cw_battery_properties[] = {
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CHARGE_COUNTER,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CURRENT_NOW,
};
static const struct power_supply_desc cw2015_bat_desc = {
.name = "cw2015-battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = cw_battery_properties,
.num_properties = ARRAY_SIZE(cw_battery_properties),
.get_property = cw_battery_get_property,
};
static int cw2015_parse_properties(struct cw_battery *cw_bat)
{
struct device *dev = cw_bat->dev;
int length;
int ret;
length = device_property_count_u8(dev, "cellwise,battery-profile");
if (length < 0) {
dev_warn(cw_bat->dev,
"No battery-profile found, using current flash contents\n");
} else if (length != CW2015_SIZE_BATINFO) {
dev_err(cw_bat->dev, "battery-profile must be %d bytes\n",
CW2015_SIZE_BATINFO);
return -EINVAL;
} else {
cw_bat->bat_profile = devm_kzalloc(dev, length, GFP_KERNEL);
if (!cw_bat->bat_profile)
return -ENOMEM;
ret = device_property_read_u8_array(dev,
"cellwise,battery-profile",
cw_bat->bat_profile,
length);
if (ret)
return ret;
}
ret = device_property_read_u32(dev, "cellwise,monitor-interval-ms",
&cw_bat->poll_interval_ms);
if (ret) {
dev_dbg(cw_bat->dev, "Using default poll interval\n");
cw_bat->poll_interval_ms = CW2015_DEFAULT_POLL_INTERVAL_MS;
}
return 0;
}
static const struct regmap_range regmap_ranges_rd_yes[] = {
regmap_reg_range(CW2015_REG_VERSION, CW2015_REG_VERSION),
regmap_reg_range(CW2015_REG_VCELL, CW2015_REG_CONFIG),
regmap_reg_range(CW2015_REG_MODE, CW2015_REG_MODE),
regmap_reg_range(CW2015_REG_BATINFO,
CW2015_REG_BATINFO + CW2015_SIZE_BATINFO - 1),
};
static const struct regmap_access_table regmap_rd_table = {
.yes_ranges = regmap_ranges_rd_yes,
.n_yes_ranges = 4,
};
static const struct regmap_range regmap_ranges_wr_yes[] = {
regmap_reg_range(CW2015_REG_RRT_ALERT, CW2015_REG_CONFIG),
regmap_reg_range(CW2015_REG_MODE, CW2015_REG_MODE),
regmap_reg_range(CW2015_REG_BATINFO,
CW2015_REG_BATINFO + CW2015_SIZE_BATINFO - 1),
};
static const struct regmap_access_table regmap_wr_table = {
.yes_ranges = regmap_ranges_wr_yes,
.n_yes_ranges = 3,
};
static const struct regmap_range regmap_ranges_vol_yes[] = {
regmap_reg_range(CW2015_REG_VCELL, CW2015_REG_SOC + 1),
};
static const struct regmap_access_table regmap_vol_table = {
.yes_ranges = regmap_ranges_vol_yes,
.n_yes_ranges = 1,
};
static const struct regmap_config cw2015_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.rd_table = &regmap_rd_table,
.wr_table = &regmap_wr_table,
.volatile_table = &regmap_vol_table,
.max_register = CW2015_REG_BATINFO + CW2015_SIZE_BATINFO - 1,
};
static int cw_bat_probe(struct i2c_client *client)
{
int ret;
struct cw_battery *cw_bat;
struct power_supply_config psy_cfg = { 0 };
cw_bat = devm_kzalloc(&client->dev, sizeof(*cw_bat), GFP_KERNEL);
if (!cw_bat)
return -ENOMEM;
i2c_set_clientdata(client, cw_bat);
cw_bat->dev = &client->dev;
cw_bat->soc = 1;
ret = cw2015_parse_properties(cw_bat);
if (ret) {
dev_err(cw_bat->dev, "Failed to parse cw2015 properties\n");
return ret;
}
cw_bat->regmap = devm_regmap_init_i2c(client, &cw2015_regmap_config);
if (IS_ERR(cw_bat->regmap)) {
dev_err(cw_bat->dev, "Failed to allocate regmap: %ld\n",
PTR_ERR(cw_bat->regmap));
return PTR_ERR(cw_bat->regmap);
}
ret = cw_init(cw_bat);
if (ret) {
dev_err(cw_bat->dev, "Init failed: %d\n", ret);
return ret;
}
psy_cfg.drv_data = cw_bat;
psy_cfg.fwnode = dev_fwnode(cw_bat->dev);
cw_bat->rk_bat = devm_power_supply_register(&client->dev,
&cw2015_bat_desc,
&psy_cfg);
if (IS_ERR(cw_bat->rk_bat)) {
dev_err(cw_bat->dev, "Failed to register power supply\n");
return PTR_ERR(cw_bat->rk_bat);
}
ret = power_supply_get_battery_info(cw_bat->rk_bat, &cw_bat->battery);
if (ret) {
dev_warn(cw_bat->dev,
"No monitored battery, some properties will be missing\n");
}
cw_bat->battery_workqueue = create_singlethread_workqueue("rk_battery");
INIT_DELAYED_WORK(&cw_bat->battery_delay_work, cw_bat_work);
queue_delayed_work(cw_bat->battery_workqueue,
&cw_bat->battery_delay_work, msecs_to_jiffies(10));
return 0;
}
static int __maybe_unused cw_bat_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct cw_battery *cw_bat = i2c_get_clientdata(client);
cancel_delayed_work_sync(&cw_bat->battery_delay_work);
return 0;
}
static int __maybe_unused cw_bat_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct cw_battery *cw_bat = i2c_get_clientdata(client);
queue_delayed_work(cw_bat->battery_workqueue,
&cw_bat->battery_delay_work, 0);
return 0;
}
static SIMPLE_DEV_PM_OPS(cw_bat_pm_ops, cw_bat_suspend, cw_bat_resume);
static int cw_bat_remove(struct i2c_client *client)
{
struct cw_battery *cw_bat = i2c_get_clientdata(client);
cancel_delayed_work_sync(&cw_bat->battery_delay_work);
power_supply_put_battery_info(cw_bat->rk_bat, &cw_bat->battery);
return 0;
}
static const struct i2c_device_id cw_bat_id_table[] = {
{ "cw2015", 0 },
{ }
};
static const struct of_device_id cw2015_of_match[] = {
{ .compatible = "cellwise,cw2015" },
{ }
};
MODULE_DEVICE_TABLE(of, cw2015_of_match);
static struct i2c_driver cw_bat_driver = {
.driver = {
.name = "cw2015",
.of_match_table = cw2015_of_match,
.pm = &cw_bat_pm_ops,
},
.probe_new = cw_bat_probe,
.remove = cw_bat_remove,
.id_table = cw_bat_id_table,
};
module_i2c_driver(cw_bat_driver);
MODULE_AUTHOR("xhc<xhc@rock-chips.com>");
MODULE_AUTHOR("Tobias Schramm <t.schramm@manjaro.org>");
MODULE_DESCRIPTION("cw2015/cw2013 battery driver");
MODULE_LICENSE("GPL");
...@@ -241,6 +241,7 @@ static int gab_probe(struct platform_device *pdev) ...@@ -241,6 +241,7 @@ static int gab_probe(struct platform_device *pdev)
struct power_supply_desc *psy_desc; struct power_supply_desc *psy_desc;
struct power_supply_config psy_cfg = {}; struct power_supply_config psy_cfg = {};
struct gab_platform_data *pdata = pdev->dev.platform_data; struct gab_platform_data *pdata = pdev->dev.platform_data;
enum power_supply_property *properties;
int ret = 0; int ret = 0;
int chan; int chan;
int index = ARRAY_SIZE(gab_props); int index = ARRAY_SIZE(gab_props);
...@@ -268,16 +269,16 @@ static int gab_probe(struct platform_device *pdev) ...@@ -268,16 +269,16 @@ static int gab_probe(struct platform_device *pdev)
* copying the static properties and allocating extra memory for holding * copying the static properties and allocating extra memory for holding
* the extra configurable properties received from platform data. * the extra configurable properties received from platform data.
*/ */
psy_desc->properties = kcalloc(ARRAY_SIZE(gab_props) + properties = kcalloc(ARRAY_SIZE(gab_props) +
ARRAY_SIZE(gab_chan_name), ARRAY_SIZE(gab_chan_name),
sizeof(*psy_desc->properties), sizeof(*properties),
GFP_KERNEL); GFP_KERNEL);
if (!psy_desc->properties) { if (!properties) {
ret = -ENOMEM; ret = -ENOMEM;
goto first_mem_fail; goto first_mem_fail;
} }
memcpy(psy_desc->properties, gab_props, sizeof(gab_props)); memcpy(properties, gab_props, sizeof(gab_props));
/* /*
* getting channel from iio and copying the battery properties * getting channel from iio and copying the battery properties
...@@ -294,13 +295,11 @@ static int gab_probe(struct platform_device *pdev) ...@@ -294,13 +295,11 @@ static int gab_probe(struct platform_device *pdev)
int index2; int index2;
for (index2 = 0; index2 < index; index2++) { for (index2 = 0; index2 < index; index2++) {
if (psy_desc->properties[index2] == if (properties[index2] == gab_dyn_props[chan])
gab_dyn_props[chan])
break; /* already known */ break; /* already known */
} }
if (index2 == index) /* really new */ if (index2 == index) /* really new */
psy_desc->properties[index++] = properties[index++] = gab_dyn_props[chan];
gab_dyn_props[chan];
any = true; any = true;
} }
} }
...@@ -317,6 +316,7 @@ static int gab_probe(struct platform_device *pdev) ...@@ -317,6 +316,7 @@ static int gab_probe(struct platform_device *pdev)
* as come channels may be not be supported by the device.So * as come channels may be not be supported by the device.So
* we need to take care of that. * we need to take care of that.
*/ */
psy_desc->properties = properties;
psy_desc->num_properties = index; psy_desc->num_properties = index;
adc_bat->psy = power_supply_register(&pdev->dev, psy_desc, &psy_cfg); adc_bat->psy = power_supply_register(&pdev->dev, psy_desc, &psy_cfg);
...@@ -358,7 +358,7 @@ static int gab_probe(struct platform_device *pdev) ...@@ -358,7 +358,7 @@ static int gab_probe(struct platform_device *pdev)
iio_channel_release(adc_bat->channel[chan]); iio_channel_release(adc_bat->channel[chan]);
} }
second_mem_fail: second_mem_fail:
kfree(psy_desc->properties); kfree(properties);
first_mem_fail: first_mem_fail:
return ret; return ret;
} }
......
...@@ -572,27 +572,14 @@ static void lp8788_setup_adc_channel(struct device *dev, ...@@ -572,27 +572,14 @@ static void lp8788_setup_adc_channel(struct device *dev,
return; return;
/* ADC channel for battery voltage */ /* ADC channel for battery voltage */
chan = iio_channel_get(dev, pdata->adc_vbatt); chan = devm_iio_channel_get(dev, pdata->adc_vbatt);
pchg->chan[LP8788_VBATT] = IS_ERR(chan) ? NULL : chan; pchg->chan[LP8788_VBATT] = IS_ERR(chan) ? NULL : chan;
/* ADC channel for battery temperature */ /* ADC channel for battery temperature */
chan = iio_channel_get(dev, pdata->adc_batt_temp); chan = devm_iio_channel_get(dev, pdata->adc_batt_temp);
pchg->chan[LP8788_BATT_TEMP] = IS_ERR(chan) ? NULL : chan; pchg->chan[LP8788_BATT_TEMP] = IS_ERR(chan) ? NULL : chan;
} }
static void lp8788_release_adc_channel(struct lp8788_charger *pchg)
{
int i;
for (i = 0; i < LP8788_NUM_CHG_ADC; i++) {
if (!pchg->chan[i])
continue;
iio_channel_release(pchg->chan[i]);
pchg->chan[i] = NULL;
}
}
static ssize_t lp8788_show_charger_status(struct device *dev, static ssize_t lp8788_show_charger_status(struct device *dev,
struct device_attribute *attr, char *buf) struct device_attribute *attr, char *buf)
{ {
...@@ -735,7 +722,6 @@ static int lp8788_charger_remove(struct platform_device *pdev) ...@@ -735,7 +722,6 @@ static int lp8788_charger_remove(struct platform_device *pdev)
flush_work(&pchg->charger_work); flush_work(&pchg->charger_work);
lp8788_irq_unregister(pdev, pchg); lp8788_irq_unregister(pdev, pchg);
lp8788_psy_unregister(pchg); lp8788_psy_unregister(pchg);
lp8788_release_adc_channel(pchg);
return 0; return 0;
} }
......
...@@ -623,9 +623,19 @@ static const struct platform_device_id max14577_charger_id[] = { ...@@ -623,9 +623,19 @@ static const struct platform_device_id max14577_charger_id[] = {
}; };
MODULE_DEVICE_TABLE(platform, max14577_charger_id); MODULE_DEVICE_TABLE(platform, max14577_charger_id);
static const struct of_device_id of_max14577_charger_dt_match[] = {
{ .compatible = "maxim,max14577-charger",
.data = (void *)MAXIM_DEVICE_TYPE_MAX14577, },
{ .compatible = "maxim,max77836-charger",
.data = (void *)MAXIM_DEVICE_TYPE_MAX77836, },
{ },
};
MODULE_DEVICE_TABLE(of, of_max14577_charger_dt_match);
static struct platform_driver max14577_charger_driver = { static struct platform_driver max14577_charger_driver = {
.driver = { .driver = {
.name = "max14577-charger", .name = "max14577-charger",
.of_match_table = of_max14577_charger_dt_match,
}, },
.probe = max14577_charger_probe, .probe = max14577_charger_probe,
.remove = max14577_charger_remove, .remove = max14577_charger_remove,
......
...@@ -139,10 +139,9 @@ static void max14656_irq_worker(struct work_struct *work) ...@@ -139,10 +139,9 @@ static void max14656_irq_worker(struct work_struct *work)
u8 buf[REG_TOTAL_NUM]; u8 buf[REG_TOTAL_NUM];
u8 chg_type; u8 chg_type;
int ret = 0;
ret = max14656_read_block_reg(chip->client, MAX14656_DEVICE_ID, max14656_read_block_reg(chip->client, MAX14656_DEVICE_ID,
REG_TOTAL_NUM, buf); REG_TOTAL_NUM, buf);
if ((buf[MAX14656_STATUS_1] & STATUS1_VB_VALID_MASK) && if ((buf[MAX14656_STATUS_1] & STATUS1_VB_VALID_MASK) &&
(buf[MAX14656_STATUS_1] & STATUS1_CHG_TYPE_MASK)) { (buf[MAX14656_STATUS_1] & STATUS1_CHG_TYPE_MASK)) {
......
...@@ -126,7 +126,7 @@ static void max17040_get_vcell(struct i2c_client *client) ...@@ -126,7 +126,7 @@ static void max17040_get_vcell(struct i2c_client *client)
vcell = max17040_read_reg(client, MAX17040_VCELL); vcell = max17040_read_reg(client, MAX17040_VCELL);
chip->vcell = vcell; chip->vcell = (vcell >> 4) * 1250;
} }
static void max17040_get_soc(struct i2c_client *client) static void max17040_get_soc(struct i2c_client *client)
......
...@@ -87,6 +87,7 @@ static enum power_supply_property max17042_battery_props[] = { ...@@ -87,6 +87,7 @@ static enum power_supply_property max17042_battery_props[] = {
POWER_SUPPLY_PROP_SCOPE, POWER_SUPPLY_PROP_SCOPE,
POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CURRENT_AVG, POWER_SUPPLY_PROP_CURRENT_AVG,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
}; };
static int max17042_get_temperature(struct max17042_chip *chip, int *temp) static int max17042_get_temperature(struct max17042_chip *chip, int *temp)
...@@ -411,6 +412,13 @@ static int max17042_get_property(struct power_supply *psy, ...@@ -411,6 +412,13 @@ static int max17042_get_property(struct power_supply *psy,
return -EINVAL; return -EINVAL;
} }
break; break;
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
ret = regmap_read(map, MAX17042_TTE, &data);
if (ret < 0)
return ret;
val->intval = data * 5625 / 1000;
break;
default: default:
return -EINVAL; return -EINVAL;
} }
......
...@@ -88,7 +88,7 @@ static enum power_supply_property olpc_ac_props[] = { ...@@ -88,7 +88,7 @@ static enum power_supply_property olpc_ac_props[] = {
}; };
static const struct power_supply_desc olpc_ac_desc = { static const struct power_supply_desc olpc_ac_desc = {
.name = "olpc-ac", .name = "olpc_ac",
.type = POWER_SUPPLY_TYPE_MAINS, .type = POWER_SUPPLY_TYPE_MAINS,
.properties = olpc_ac_props, .properties = olpc_ac_props,
.num_properties = ARRAY_SIZE(olpc_ac_props), .num_properties = ARRAY_SIZE(olpc_ac_props),
...@@ -605,7 +605,7 @@ static const struct attribute_group *olpc_bat_sysfs_groups[] = { ...@@ -605,7 +605,7 @@ static const struct attribute_group *olpc_bat_sysfs_groups[] = {
*********************************************************************/ *********************************************************************/
static struct power_supply_desc olpc_bat_desc = { static struct power_supply_desc olpc_bat_desc = {
.name = "olpc-battery", .name = "olpc_battery",
.get_property = olpc_bat_get_property, .get_property = olpc_bat_get_property,
.use_for_apm = 1, .use_for_apm = 1,
}; };
......
...@@ -620,10 +620,18 @@ int power_supply_get_battery_info(struct power_supply *psy, ...@@ -620,10 +620,18 @@ int power_supply_get_battery_info(struct power_supply *psy,
&info->voltage_min_design_uv); &info->voltage_min_design_uv);
of_property_read_u32(battery_np, "voltage-max-design-microvolt", of_property_read_u32(battery_np, "voltage-max-design-microvolt",
&info->voltage_max_design_uv); &info->voltage_max_design_uv);
of_property_read_u32(battery_np, "trickle-charge-current-microamp",
&info->tricklecharge_current_ua);
of_property_read_u32(battery_np, "precharge-current-microamp", of_property_read_u32(battery_np, "precharge-current-microamp",
&info->precharge_current_ua); &info->precharge_current_ua);
of_property_read_u32(battery_np, "precharge-upper-limit-microvolt",
&info->precharge_voltage_max_uv);
of_property_read_u32(battery_np, "charge-term-current-microamp", of_property_read_u32(battery_np, "charge-term-current-microamp",
&info->charge_term_current_ua); &info->charge_term_current_ua);
of_property_read_u32(battery_np, "re-charge-voltage-microvolt",
&info->charge_restart_voltage_uv);
of_property_read_u32(battery_np, "over-voltage-threshold-microvolt",
&info->overvoltage_limit_uv);
of_property_read_u32(battery_np, "constant-charge-current-max-microamp", of_property_read_u32(battery_np, "constant-charge-current-max-microamp",
&info->constant_charge_current_max_ua); &info->constant_charge_current_max_ua);
of_property_read_u32(battery_np, "constant-charge-voltage-max-microvolt", of_property_read_u32(battery_np, "constant-charge-voltage-max-microvolt",
......
...@@ -13,6 +13,11 @@ struct power_supply_hwmon { ...@@ -13,6 +13,11 @@ struct power_supply_hwmon {
unsigned long *props; unsigned long *props;
}; };
static const char *const ps_temp_label[] = {
"temp",
"ambient temp",
};
static int power_supply_hwmon_in_to_property(u32 attr) static int power_supply_hwmon_in_to_property(u32 attr)
{ {
switch (attr) { switch (attr) {
...@@ -98,6 +103,39 @@ static bool power_supply_hwmon_is_a_label(enum hwmon_sensor_types type, ...@@ -98,6 +103,39 @@ static bool power_supply_hwmon_is_a_label(enum hwmon_sensor_types type,
return type == hwmon_temp && attr == hwmon_temp_label; return type == hwmon_temp && attr == hwmon_temp_label;
} }
struct hwmon_type_attr_list {
const u32 *attrs;
size_t n_attrs;
};
static const u32 ps_temp_attrs[] = {
hwmon_temp_input,
hwmon_temp_min, hwmon_temp_max,
hwmon_temp_min_alarm, hwmon_temp_max_alarm,
};
static const struct hwmon_type_attr_list ps_type_attrs[hwmon_max] = {
[hwmon_temp] = { ps_temp_attrs, ARRAY_SIZE(ps_temp_attrs) },
};
static bool power_supply_hwmon_has_input(
const struct power_supply_hwmon *psyhw,
enum hwmon_sensor_types type, int channel)
{
const struct hwmon_type_attr_list *attr_list = &ps_type_attrs[type];
size_t i;
for (i = 0; i < attr_list->n_attrs; ++i) {
int prop = power_supply_hwmon_to_property(type,
attr_list->attrs[i], channel);
if (prop >= 0 && test_bit(prop, psyhw->props))
return true;
}
return false;
}
static bool power_supply_hwmon_is_writable(enum hwmon_sensor_types type, static bool power_supply_hwmon_is_writable(enum hwmon_sensor_types type,
u32 attr) u32 attr)
{ {
...@@ -124,9 +162,12 @@ static umode_t power_supply_hwmon_is_visible(const void *data, ...@@ -124,9 +162,12 @@ static umode_t power_supply_hwmon_is_visible(const void *data,
const struct power_supply_hwmon *psyhw = data; const struct power_supply_hwmon *psyhw = data;
int prop; int prop;
if (power_supply_hwmon_is_a_label(type, attr)) {
if (power_supply_hwmon_is_a_label(type, attr)) if (power_supply_hwmon_has_input(psyhw, type, channel))
return 0444; return 0444;
else
return 0;
}
prop = power_supply_hwmon_to_property(type, attr, channel); prop = power_supply_hwmon_to_property(type, attr, channel);
if (prop < 0 || !test_bit(prop, psyhw->props)) if (prop < 0 || !test_bit(prop, psyhw->props))
...@@ -144,7 +185,20 @@ static int power_supply_hwmon_read_string(struct device *dev, ...@@ -144,7 +185,20 @@ static int power_supply_hwmon_read_string(struct device *dev,
u32 attr, int channel, u32 attr, int channel,
const char **str) const char **str)
{ {
*str = channel ? "temp" : "temp ambient"; switch (type) {
case hwmon_temp:
*str = ps_temp_label[channel];
break;
default:
/* unreachable, but see:
* gcc bug #51513 [1] and clang bug #978 [2]
*
* [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51513
* [2] https://github.com/ClangBuiltLinux/linux/issues/978
*/
break;
}
return 0; return 0;
} }
...@@ -304,7 +358,7 @@ int power_supply_add_hwmon_sysfs(struct power_supply *psy) ...@@ -304,7 +358,7 @@ int power_supply_add_hwmon_sysfs(struct power_supply *psy)
goto error; goto error;
} }
ret = devm_add_action(dev, power_supply_hwmon_bitmap_free, ret = devm_add_action_or_reset(dev, power_supply_hwmon_bitmap_free,
psyhw->props); psyhw->props);
if (ret) if (ret)
goto error; goto error;
......
...@@ -18,68 +18,211 @@ ...@@ -18,68 +18,211 @@
#include "power_supply.h" #include "power_supply.h"
/* #define MAX_PROP_NAME_LEN 30
* This is because the name "current" breaks the device attr macro.
* The "current" word resolves to "(get_current())" so instead of struct power_supply_attr {
* "current" "(get_current())" appears in the sysfs. const char *prop_name;
* char attr_name[MAX_PROP_NAME_LEN + 1];
* The source of this definition is the device.h which calls __ATTR struct device_attribute dev_attr;
* macro in sysfs.h which calls the __stringify macro. const char * const *text_values;
* int text_values_len;
* Only modification that the name is not tried to be resolved };
* (as a macro let's say).
*/
#define POWER_SUPPLY_ATTR(_name) \ #define _POWER_SUPPLY_ATTR(_name, _text, _len) \
{ \ [POWER_SUPPLY_PROP_ ## _name] = \
.attr = { .name = #_name }, \ { \
.show = power_supply_show_property, \ .prop_name = #_name, \
.store = power_supply_store_property, \ .attr_name = #_name "\0", \
.text_values = _text, \
.text_values_len = _len, \
} }
static struct device_attribute power_supply_attrs[]; #define POWER_SUPPLY_ATTR(_name) _POWER_SUPPLY_ATTR(_name, NULL, 0)
#define _POWER_SUPPLY_ENUM_ATTR(_name, _text) \
_POWER_SUPPLY_ATTR(_name, _text, ARRAY_SIZE(_text))
#define POWER_SUPPLY_ENUM_ATTR(_name) \
_POWER_SUPPLY_ENUM_ATTR(_name, POWER_SUPPLY_ ## _name ## _TEXT)
static const char * const POWER_SUPPLY_TYPE_TEXT[] = {
[POWER_SUPPLY_TYPE_UNKNOWN] = "Unknown",
[POWER_SUPPLY_TYPE_BATTERY] = "Battery",
[POWER_SUPPLY_TYPE_UPS] = "UPS",
[POWER_SUPPLY_TYPE_MAINS] = "Mains",
[POWER_SUPPLY_TYPE_USB] = "USB",
[POWER_SUPPLY_TYPE_USB_DCP] = "USB_DCP",
[POWER_SUPPLY_TYPE_USB_CDP] = "USB_CDP",
[POWER_SUPPLY_TYPE_USB_ACA] = "USB_ACA",
[POWER_SUPPLY_TYPE_USB_TYPE_C] = "USB_C",
[POWER_SUPPLY_TYPE_USB_PD] = "USB_PD",
[POWER_SUPPLY_TYPE_USB_PD_DRP] = "USB_PD_DRP",
[POWER_SUPPLY_TYPE_APPLE_BRICK_ID] = "BrickID",
};
static const char * const power_supply_type_text[] = { static const char * const POWER_SUPPLY_USB_TYPE_TEXT[] = {
"Unknown", "Battery", "UPS", "Mains", "USB", [POWER_SUPPLY_USB_TYPE_UNKNOWN] = "Unknown",
"USB_DCP", "USB_CDP", "USB_ACA", "USB_C", [POWER_SUPPLY_USB_TYPE_SDP] = "SDP",
"USB_PD", "USB_PD_DRP", "BrickID" [POWER_SUPPLY_USB_TYPE_DCP] = "DCP",
[POWER_SUPPLY_USB_TYPE_CDP] = "CDP",
[POWER_SUPPLY_USB_TYPE_ACA] = "ACA",
[POWER_SUPPLY_USB_TYPE_C] = "C",
[POWER_SUPPLY_USB_TYPE_PD] = "PD",
[POWER_SUPPLY_USB_TYPE_PD_DRP] = "PD_DRP",
[POWER_SUPPLY_USB_TYPE_PD_PPS] = "PD_PPS",
[POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID] = "BrickID",
}; };
static const char * const power_supply_usb_type_text[] = { static const char * const POWER_SUPPLY_STATUS_TEXT[] = {
"Unknown", "SDP", "DCP", "CDP", "ACA", "C", [POWER_SUPPLY_STATUS_UNKNOWN] = "Unknown",
"PD", "PD_DRP", "PD_PPS", "BrickID" [POWER_SUPPLY_STATUS_CHARGING] = "Charging",
[POWER_SUPPLY_STATUS_DISCHARGING] = "Discharging",
[POWER_SUPPLY_STATUS_NOT_CHARGING] = "Not charging",
[POWER_SUPPLY_STATUS_FULL] = "Full",
}; };
static const char * const power_supply_status_text[] = { static const char * const POWER_SUPPLY_CHARGE_TYPE_TEXT[] = {
"Unknown", "Charging", "Discharging", "Not charging", "Full" [POWER_SUPPLY_CHARGE_TYPE_UNKNOWN] = "Unknown",
[POWER_SUPPLY_CHARGE_TYPE_NONE] = "N/A",
[POWER_SUPPLY_CHARGE_TYPE_TRICKLE] = "Trickle",
[POWER_SUPPLY_CHARGE_TYPE_FAST] = "Fast",
[POWER_SUPPLY_CHARGE_TYPE_STANDARD] = "Standard",
[POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE] = "Adaptive",
[POWER_SUPPLY_CHARGE_TYPE_CUSTOM] = "Custom",
}; };
static const char * const power_supply_charge_type_text[] = { static const char * const POWER_SUPPLY_HEALTH_TEXT[] = {
"Unknown", "N/A", "Trickle", "Fast", "Standard", "Adaptive", "Custom" [POWER_SUPPLY_HEALTH_UNKNOWN] = "Unknown",
[POWER_SUPPLY_HEALTH_GOOD] = "Good",
[POWER_SUPPLY_HEALTH_OVERHEAT] = "Overheat",
[POWER_SUPPLY_HEALTH_DEAD] = "Dead",
[POWER_SUPPLY_HEALTH_OVERVOLTAGE] = "Over voltage",
[POWER_SUPPLY_HEALTH_UNSPEC_FAILURE] = "Unspecified failure",
[POWER_SUPPLY_HEALTH_COLD] = "Cold",
[POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE] = "Watchdog timer expire",
[POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE] = "Safety timer expire",
[POWER_SUPPLY_HEALTH_OVERCURRENT] = "Over current",
[POWER_SUPPLY_HEALTH_CALIBRATION_REQUIRED] = "Calibration required",
}; };
static const char * const power_supply_health_text[] = { static const char * const POWER_SUPPLY_TECHNOLOGY_TEXT[] = {
"Unknown", "Good", "Overheat", "Dead", "Over voltage", [POWER_SUPPLY_TECHNOLOGY_UNKNOWN] = "Unknown",
"Unspecified failure", "Cold", "Watchdog timer expire", [POWER_SUPPLY_TECHNOLOGY_NiMH] = "NiMH",
"Safety timer expire", "Over current" [POWER_SUPPLY_TECHNOLOGY_LION] = "Li-ion",
[POWER_SUPPLY_TECHNOLOGY_LIPO] = "Li-poly",
[POWER_SUPPLY_TECHNOLOGY_LiFe] = "LiFe",
[POWER_SUPPLY_TECHNOLOGY_NiCd] = "NiCd",
[POWER_SUPPLY_TECHNOLOGY_LiMn] = "LiMn",
}; };
static const char * const power_supply_technology_text[] = { static const char * const POWER_SUPPLY_CAPACITY_LEVEL_TEXT[] = {
"Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd", [POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN] = "Unknown",
"LiMn" [POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL] = "Critical",
[POWER_SUPPLY_CAPACITY_LEVEL_LOW] = "Low",
[POWER_SUPPLY_CAPACITY_LEVEL_NORMAL] = "Normal",
[POWER_SUPPLY_CAPACITY_LEVEL_HIGH] = "High",
[POWER_SUPPLY_CAPACITY_LEVEL_FULL] = "Full",
}; };
static const char * const power_supply_capacity_level_text[] = { static const char * const POWER_SUPPLY_SCOPE_TEXT[] = {
"Unknown", "Critical", "Low", "Normal", "High", "Full" [POWER_SUPPLY_SCOPE_UNKNOWN] = "Unknown",
[POWER_SUPPLY_SCOPE_SYSTEM] = "System",
[POWER_SUPPLY_SCOPE_DEVICE] = "Device",
}; };
static const char * const power_supply_scope_text[] = { static struct power_supply_attr power_supply_attrs[] = {
"Unknown", "System", "Device" /* Properties of type `int' */
POWER_SUPPLY_ENUM_ATTR(STATUS),
POWER_SUPPLY_ENUM_ATTR(CHARGE_TYPE),
POWER_SUPPLY_ENUM_ATTR(HEALTH),
POWER_SUPPLY_ATTR(PRESENT),
POWER_SUPPLY_ATTR(ONLINE),
POWER_SUPPLY_ATTR(AUTHENTIC),
POWER_SUPPLY_ENUM_ATTR(TECHNOLOGY),
POWER_SUPPLY_ATTR(CYCLE_COUNT),
POWER_SUPPLY_ATTR(VOLTAGE_MAX),
POWER_SUPPLY_ATTR(VOLTAGE_MIN),
POWER_SUPPLY_ATTR(VOLTAGE_MAX_DESIGN),
POWER_SUPPLY_ATTR(VOLTAGE_MIN_DESIGN),
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),
POWER_SUPPLY_ATTR(CHARGE_EMPTY_DESIGN),
POWER_SUPPLY_ATTR(CHARGE_FULL),
POWER_SUPPLY_ATTR(CHARGE_EMPTY),
POWER_SUPPLY_ATTR(CHARGE_NOW),
POWER_SUPPLY_ATTR(CHARGE_AVG),
POWER_SUPPLY_ATTR(CHARGE_COUNTER),
POWER_SUPPLY_ATTR(CONSTANT_CHARGE_CURRENT),
POWER_SUPPLY_ATTR(CONSTANT_CHARGE_CURRENT_MAX),
POWER_SUPPLY_ATTR(CONSTANT_CHARGE_VOLTAGE),
POWER_SUPPLY_ATTR(CONSTANT_CHARGE_VOLTAGE_MAX),
POWER_SUPPLY_ATTR(CHARGE_CONTROL_LIMIT),
POWER_SUPPLY_ATTR(CHARGE_CONTROL_LIMIT_MAX),
POWER_SUPPLY_ATTR(CHARGE_CONTROL_START_THRESHOLD),
POWER_SUPPLY_ATTR(CHARGE_CONTROL_END_THRESHOLD),
POWER_SUPPLY_ATTR(INPUT_CURRENT_LIMIT),
POWER_SUPPLY_ATTR(INPUT_VOLTAGE_LIMIT),
POWER_SUPPLY_ATTR(INPUT_POWER_LIMIT),
POWER_SUPPLY_ATTR(ENERGY_FULL_DESIGN),
POWER_SUPPLY_ATTR(ENERGY_EMPTY_DESIGN),
POWER_SUPPLY_ATTR(ENERGY_FULL),
POWER_SUPPLY_ATTR(ENERGY_EMPTY),
POWER_SUPPLY_ATTR(ENERGY_NOW),
POWER_SUPPLY_ATTR(ENERGY_AVG),
POWER_SUPPLY_ATTR(CAPACITY),
POWER_SUPPLY_ATTR(CAPACITY_ALERT_MIN),
POWER_SUPPLY_ATTR(CAPACITY_ALERT_MAX),
POWER_SUPPLY_ATTR(CAPACITY_ERROR_MARGIN),
POWER_SUPPLY_ENUM_ATTR(CAPACITY_LEVEL),
POWER_SUPPLY_ATTR(TEMP),
POWER_SUPPLY_ATTR(TEMP_MAX),
POWER_SUPPLY_ATTR(TEMP_MIN),
POWER_SUPPLY_ATTR(TEMP_ALERT_MIN),
POWER_SUPPLY_ATTR(TEMP_ALERT_MAX),
POWER_SUPPLY_ATTR(TEMP_AMBIENT),
POWER_SUPPLY_ATTR(TEMP_AMBIENT_ALERT_MIN),
POWER_SUPPLY_ATTR(TEMP_AMBIENT_ALERT_MAX),
POWER_SUPPLY_ATTR(TIME_TO_EMPTY_NOW),
POWER_SUPPLY_ATTR(TIME_TO_EMPTY_AVG),
POWER_SUPPLY_ATTR(TIME_TO_FULL_NOW),
POWER_SUPPLY_ATTR(TIME_TO_FULL_AVG),
POWER_SUPPLY_ENUM_ATTR(TYPE),
POWER_SUPPLY_ATTR(USB_TYPE),
POWER_SUPPLY_ENUM_ATTR(SCOPE),
POWER_SUPPLY_ATTR(PRECHARGE_CURRENT),
POWER_SUPPLY_ATTR(CHARGE_TERM_CURRENT),
POWER_SUPPLY_ATTR(CALIBRATE),
POWER_SUPPLY_ATTR(MANUFACTURE_YEAR),
POWER_SUPPLY_ATTR(MANUFACTURE_MONTH),
POWER_SUPPLY_ATTR(MANUFACTURE_DAY),
/* Properties of type `const char *' */
POWER_SUPPLY_ATTR(MODEL_NAME),
POWER_SUPPLY_ATTR(MANUFACTURER),
POWER_SUPPLY_ATTR(SERIAL_NUMBER),
}; };
static struct attribute *
__power_supply_attrs[ARRAY_SIZE(power_supply_attrs) + 1];
static struct power_supply_attr *to_ps_attr(struct device_attribute *attr)
{
return container_of(attr, struct power_supply_attr, dev_attr);
}
static enum power_supply_property dev_attr_psp(struct device_attribute *attr)
{
return to_ps_attr(attr) - power_supply_attrs;
}
static ssize_t power_supply_show_usb_type(struct device *dev, static ssize_t power_supply_show_usb_type(struct device *dev,
enum power_supply_usb_type *usb_types, const struct power_supply_desc *desc,
ssize_t num_usb_types,
union power_supply_propval *value, union power_supply_propval *value,
char *buf) char *buf)
{ {
...@@ -88,16 +231,16 @@ static ssize_t power_supply_show_usb_type(struct device *dev, ...@@ -88,16 +231,16 @@ static ssize_t power_supply_show_usb_type(struct device *dev,
bool match = false; bool match = false;
int i; int i;
for (i = 0; i < num_usb_types; ++i) { for (i = 0; i < desc->num_usb_types; ++i) {
usb_type = usb_types[i]; usb_type = desc->usb_types[i];
if (value->intval == usb_type) { if (value->intval == usb_type) {
count += sprintf(buf + count, "[%s] ", count += sprintf(buf + count, "[%s] ",
power_supply_usb_type_text[usb_type]); POWER_SUPPLY_USB_TYPE_TEXT[usb_type]);
match = true; match = true;
} else { } else {
count += sprintf(buf + count, "%s ", count += sprintf(buf + count, "%s ",
power_supply_usb_type_text[usb_type]); POWER_SUPPLY_USB_TYPE_TEXT[usb_type]);
} }
} }
...@@ -117,7 +260,8 @@ static ssize_t power_supply_show_property(struct device *dev, ...@@ -117,7 +260,8 @@ static ssize_t power_supply_show_property(struct device *dev,
char *buf) { char *buf) {
ssize_t ret; ssize_t ret;
struct power_supply *psy = dev_get_drvdata(dev); struct power_supply *psy = dev_get_drvdata(dev);
enum power_supply_property psp = attr - power_supply_attrs; struct power_supply_attr *ps_attr = to_ps_attr(attr);
enum power_supply_property psp = dev_attr_psp(attr);
union power_supply_propval value; union power_supply_propval value;
if (psp == POWER_SUPPLY_PROP_TYPE) { if (psp == POWER_SUPPLY_PROP_TYPE) {
...@@ -137,39 +281,15 @@ static ssize_t power_supply_show_property(struct device *dev, ...@@ -137,39 +281,15 @@ static ssize_t power_supply_show_property(struct device *dev,
} }
} }
if (ps_attr->text_values_len > 0 &&
value.intval < ps_attr->text_values_len && value.intval >= 0) {
return sprintf(buf, "%s\n", ps_attr->text_values[value.intval]);
}
switch (psp) { switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
ret = sprintf(buf, "%s\n",
power_supply_status_text[value.intval]);
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
ret = sprintf(buf, "%s\n",
power_supply_charge_type_text[value.intval]);
break;
case POWER_SUPPLY_PROP_HEALTH:
ret = sprintf(buf, "%s\n",
power_supply_health_text[value.intval]);
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
ret = sprintf(buf, "%s\n",
power_supply_technology_text[value.intval]);
break;
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
ret = sprintf(buf, "%s\n",
power_supply_capacity_level_text[value.intval]);
break;
case POWER_SUPPLY_PROP_TYPE:
ret = sprintf(buf, "%s\n",
power_supply_type_text[value.intval]);
break;
case POWER_SUPPLY_PROP_USB_TYPE: case POWER_SUPPLY_PROP_USB_TYPE:
ret = power_supply_show_usb_type(dev, psy->desc->usb_types, ret = power_supply_show_usb_type(dev, psy->desc,
psy->desc->num_usb_types, &value, buf);
&value, buf);
break;
case POWER_SUPPLY_PROP_SCOPE:
ret = sprintf(buf, "%s\n",
power_supply_scope_text[value.intval]);
break; break;
case POWER_SUPPLY_PROP_MODEL_NAME ... POWER_SUPPLY_PROP_SERIAL_NUMBER: case POWER_SUPPLY_PROP_MODEL_NAME ... POWER_SUPPLY_PROP_SERIAL_NUMBER:
ret = sprintf(buf, "%s\n", value.strval); ret = sprintf(buf, "%s\n", value.strval);
...@@ -186,30 +306,14 @@ static ssize_t power_supply_store_property(struct device *dev, ...@@ -186,30 +306,14 @@ static ssize_t power_supply_store_property(struct device *dev,
const char *buf, size_t count) { const char *buf, size_t count) {
ssize_t ret; ssize_t ret;
struct power_supply *psy = dev_get_drvdata(dev); struct power_supply *psy = dev_get_drvdata(dev);
enum power_supply_property psp = attr - power_supply_attrs; struct power_supply_attr *ps_attr = to_ps_attr(attr);
enum power_supply_property psp = dev_attr_psp(attr);
union power_supply_propval value; union power_supply_propval value;
switch (psp) { ret = -EINVAL;
case POWER_SUPPLY_PROP_STATUS: if (ps_attr->text_values_len > 0) {
ret = sysfs_match_string(power_supply_status_text, buf); ret = __sysfs_match_string(ps_attr->text_values,
break; ps_attr->text_values_len, buf);
case POWER_SUPPLY_PROP_CHARGE_TYPE:
ret = sysfs_match_string(power_supply_charge_type_text, buf);
break;
case POWER_SUPPLY_PROP_HEALTH:
ret = sysfs_match_string(power_supply_health_text, buf);
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
ret = sysfs_match_string(power_supply_technology_text, buf);
break;
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
ret = sysfs_match_string(power_supply_capacity_level_text, buf);
break;
case POWER_SUPPLY_PROP_SCOPE:
ret = sysfs_match_string(power_supply_scope_text, buf);
break;
default:
ret = -EINVAL;
} }
/* /*
...@@ -235,86 +339,6 @@ static ssize_t power_supply_store_property(struct device *dev, ...@@ -235,86 +339,6 @@ static ssize_t power_supply_store_property(struct device *dev,
return count; return count;
} }
/* Must be in the same order as POWER_SUPPLY_PROP_* */
static struct device_attribute power_supply_attrs[] = {
/* Properties of type `int' */
POWER_SUPPLY_ATTR(status),
POWER_SUPPLY_ATTR(charge_type),
POWER_SUPPLY_ATTR(health),
POWER_SUPPLY_ATTR(present),
POWER_SUPPLY_ATTR(online),
POWER_SUPPLY_ATTR(authentic),
POWER_SUPPLY_ATTR(technology),
POWER_SUPPLY_ATTR(cycle_count),
POWER_SUPPLY_ATTR(voltage_max),
POWER_SUPPLY_ATTR(voltage_min),
POWER_SUPPLY_ATTR(voltage_max_design),
POWER_SUPPLY_ATTR(voltage_min_design),
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),
POWER_SUPPLY_ATTR(charge_empty_design),
POWER_SUPPLY_ATTR(charge_full),
POWER_SUPPLY_ATTR(charge_empty),
POWER_SUPPLY_ATTR(charge_now),
POWER_SUPPLY_ATTR(charge_avg),
POWER_SUPPLY_ATTR(charge_counter),
POWER_SUPPLY_ATTR(constant_charge_current),
POWER_SUPPLY_ATTR(constant_charge_current_max),
POWER_SUPPLY_ATTR(constant_charge_voltage),
POWER_SUPPLY_ATTR(constant_charge_voltage_max),
POWER_SUPPLY_ATTR(charge_control_limit),
POWER_SUPPLY_ATTR(charge_control_limit_max),
POWER_SUPPLY_ATTR(charge_control_start_threshold),
POWER_SUPPLY_ATTR(charge_control_end_threshold),
POWER_SUPPLY_ATTR(input_current_limit),
POWER_SUPPLY_ATTR(input_voltage_limit),
POWER_SUPPLY_ATTR(input_power_limit),
POWER_SUPPLY_ATTR(energy_full_design),
POWER_SUPPLY_ATTR(energy_empty_design),
POWER_SUPPLY_ATTR(energy_full),
POWER_SUPPLY_ATTR(energy_empty),
POWER_SUPPLY_ATTR(energy_now),
POWER_SUPPLY_ATTR(energy_avg),
POWER_SUPPLY_ATTR(capacity),
POWER_SUPPLY_ATTR(capacity_alert_min),
POWER_SUPPLY_ATTR(capacity_alert_max),
POWER_SUPPLY_ATTR(capacity_level),
POWER_SUPPLY_ATTR(temp),
POWER_SUPPLY_ATTR(temp_max),
POWER_SUPPLY_ATTR(temp_min),
POWER_SUPPLY_ATTR(temp_alert_min),
POWER_SUPPLY_ATTR(temp_alert_max),
POWER_SUPPLY_ATTR(temp_ambient),
POWER_SUPPLY_ATTR(temp_ambient_alert_min),
POWER_SUPPLY_ATTR(temp_ambient_alert_max),
POWER_SUPPLY_ATTR(time_to_empty_now),
POWER_SUPPLY_ATTR(time_to_empty_avg),
POWER_SUPPLY_ATTR(time_to_full_now),
POWER_SUPPLY_ATTR(time_to_full_avg),
POWER_SUPPLY_ATTR(type),
POWER_SUPPLY_ATTR(usb_type),
POWER_SUPPLY_ATTR(scope),
POWER_SUPPLY_ATTR(precharge_current),
POWER_SUPPLY_ATTR(charge_term_current),
POWER_SUPPLY_ATTR(calibrate),
/* Properties of type `const char *' */
POWER_SUPPLY_ATTR(model_name),
POWER_SUPPLY_ATTR(manufacturer),
POWER_SUPPLY_ATTR(serial_number),
};
static struct attribute *
__power_supply_attrs[ARRAY_SIZE(power_supply_attrs) + 1];
static umode_t power_supply_attr_is_visible(struct kobject *kobj, static umode_t power_supply_attr_is_visible(struct kobject *kobj,
struct attribute *attr, struct attribute *attr,
int attrno) int attrno)
...@@ -324,6 +348,9 @@ static umode_t power_supply_attr_is_visible(struct kobject *kobj, ...@@ -324,6 +348,9 @@ static umode_t power_supply_attr_is_visible(struct kobject *kobj,
umode_t mode = S_IRUSR | S_IRGRP | S_IROTH; umode_t mode = S_IRUSR | S_IRGRP | S_IROTH;
int i; int i;
if (!power_supply_attrs[attrno].prop_name)
return 0;
if (attrno == POWER_SUPPLY_PROP_TYPE) if (attrno == POWER_SUPPLY_PROP_TYPE)
return mode; return mode;
...@@ -352,31 +379,69 @@ static const struct attribute_group *power_supply_attr_groups[] = { ...@@ -352,31 +379,69 @@ static const struct attribute_group *power_supply_attr_groups[] = {
NULL, NULL,
}; };
static void str_to_lower(char *str)
{
while (*str) {
*str = tolower(*str);
str++;
}
}
void power_supply_init_attrs(struct device_type *dev_type) void power_supply_init_attrs(struct device_type *dev_type)
{ {
int i; int i;
dev_type->groups = power_supply_attr_groups; dev_type->groups = power_supply_attr_groups;
for (i = 0; i < ARRAY_SIZE(power_supply_attrs); i++) for (i = 0; i < ARRAY_SIZE(power_supply_attrs); i++) {
__power_supply_attrs[i] = &power_supply_attrs[i].attr; struct device_attribute *attr;
}
static char *kstruprdup(const char *str, gfp_t gfp) if (!power_supply_attrs[i].prop_name) {
{ pr_warn("%s: Property %d skipped because is is missing from power_supply_attrs\n",
char *ret, *ustr; __func__, i);
sprintf(power_supply_attrs[i].attr_name, "_err_%d", i);
} else {
str_to_lower(power_supply_attrs[i].attr_name);
}
ustr = ret = kmalloc(strlen(str) + 1, gfp); attr = &power_supply_attrs[i].dev_attr;
if (!ret) attr->attr.name = power_supply_attrs[i].attr_name;
return NULL; attr->show = power_supply_show_property;
attr->store = power_supply_store_property;
__power_supply_attrs[i] = &attr->attr;
}
}
static int add_prop_uevent(struct device *dev, struct kobj_uevent_env *env,
enum power_supply_property prop, char *prop_buf)
{
int ret = 0;
struct power_supply_attr *pwr_attr;
struct device_attribute *dev_attr;
char *line;
pwr_attr = &power_supply_attrs[prop];
dev_attr = &pwr_attr->dev_attr;
ret = power_supply_show_property(dev, dev_attr, prop_buf);
if (ret == -ENODEV || ret == -ENODATA) {
/*
* When a battery is absent, we expect -ENODEV. Don't abort;
* send the uevent with at least the the PRESENT=0 property
*/
return 0;
}
while (*str) if (ret < 0)
*ustr++ = toupper(*str++); return ret;
*ustr = 0; line = strchr(prop_buf, '\n');
if (line)
*line = 0;
return ret; return add_uevent_var(env, "POWER_SUPPLY_%s=%s",
pwr_attr->prop_name, prop_buf);
} }
int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env) int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env)
...@@ -384,7 +449,6 @@ int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env) ...@@ -384,7 +449,6 @@ int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env)
struct power_supply *psy = dev_get_drvdata(dev); struct power_supply *psy = dev_get_drvdata(dev);
int ret = 0, j; int ret = 0, j;
char *prop_buf; char *prop_buf;
char *attrname;
if (!psy || !psy->desc) { if (!psy || !psy->desc) {
dev_dbg(dev, "No power supply yet\n"); dev_dbg(dev, "No power supply yet\n");
...@@ -399,35 +463,13 @@ int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env) ...@@ -399,35 +463,13 @@ int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env)
if (!prop_buf) if (!prop_buf)
return -ENOMEM; return -ENOMEM;
for (j = 0; j < psy->desc->num_properties; j++) { ret = add_prop_uevent(dev, env, POWER_SUPPLY_PROP_TYPE, prop_buf);
struct device_attribute *attr; if (ret)
char *line; goto out;
attr = &power_supply_attrs[psy->desc->properties[j]];
ret = power_supply_show_property(dev, attr, prop_buf);
if (ret == -ENODEV || ret == -ENODATA) {
/* When a battery is absent, we expect -ENODEV. Don't abort;
send the uevent with at least the the PRESENT=0 property */
ret = 0;
continue;
}
if (ret < 0)
goto out;
line = strchr(prop_buf, '\n');
if (line)
*line = 0;
attrname = kstruprdup(attr->attr.name, GFP_KERNEL);
if (!attrname) {
ret = -ENOMEM;
goto out;
}
ret = add_uevent_var(env, "POWER_SUPPLY_%s=%s", attrname, prop_buf); for (j = 0; j < psy->desc->num_properties; j++) {
kfree(attrname); ret = add_prop_uevent(dev, env, psy->desc->properties[j],
prop_buf);
if (ret) if (ret)
goto out; goto out;
} }
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
#include <linux/interrupt.h> #include <linux/interrupt.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of.h> #include <linux/property.h>
#include <linux/of_device.h> #include <linux/of_device.h>
#include <linux/power/sbs-battery.h> #include <linux/power/sbs-battery.h>
#include <linux/power_supply.h> #include <linux/power_supply.h>
...@@ -23,9 +23,12 @@ ...@@ -23,9 +23,12 @@
enum { enum {
REG_MANUFACTURER_DATA, REG_MANUFACTURER_DATA,
REG_BATTERY_MODE,
REG_TEMPERATURE, REG_TEMPERATURE,
REG_VOLTAGE, REG_VOLTAGE,
REG_CURRENT, REG_CURRENT_NOW,
REG_CURRENT_AVG,
REG_MAX_ERR,
REG_CAPACITY, REG_CAPACITY,
REG_TIME_TO_EMPTY, REG_TIME_TO_EMPTY,
REG_TIME_TO_FULL, REG_TIME_TO_FULL,
...@@ -41,10 +44,15 @@ enum { ...@@ -41,10 +44,15 @@ enum {
REG_DESIGN_CAPACITY_CHARGE, REG_DESIGN_CAPACITY_CHARGE,
REG_DESIGN_VOLTAGE_MIN, REG_DESIGN_VOLTAGE_MIN,
REG_DESIGN_VOLTAGE_MAX, REG_DESIGN_VOLTAGE_MAX,
REG_CHEMISTRY,
REG_MANUFACTURER, REG_MANUFACTURER,
REG_MODEL_NAME, REG_MODEL_NAME,
REG_CHARGE_CURRENT,
REG_CHARGE_VOLTAGE,
}; };
#define REG_ADDR_MANUFACTURE_DATE 0x1B
/* Battery Mode defines */ /* Battery Mode defines */
#define BATTERY_MODE_OFFSET 0x03 #define BATTERY_MODE_OFFSET 0x03
#define BATTERY_MODE_CAPACITY_MASK BIT(15) #define BATTERY_MODE_CAPACITY_MASK BIT(15)
...@@ -52,6 +60,7 @@ enum sbs_capacity_mode { ...@@ -52,6 +60,7 @@ enum sbs_capacity_mode {
CAPACITY_MODE_AMPS = 0, CAPACITY_MODE_AMPS = 0,
CAPACITY_MODE_WATTS = BATTERY_MODE_CAPACITY_MASK CAPACITY_MODE_WATTS = BATTERY_MODE_CAPACITY_MASK
}; };
#define BATTERY_MODE_CHARGER_MASK (1<<14)
/* manufacturer access defines */ /* manufacturer access defines */
#define MANUFACTURER_ACCESS_STATUS 0x0006 #define MANUFACTURER_ACCESS_STATUS 0x0006
...@@ -79,12 +88,18 @@ static const struct chip_data { ...@@ -79,12 +88,18 @@ static const struct chip_data {
} sbs_data[] = { } sbs_data[] = {
[REG_MANUFACTURER_DATA] = [REG_MANUFACTURER_DATA] =
SBS_DATA(POWER_SUPPLY_PROP_PRESENT, 0x00, 0, 65535), SBS_DATA(POWER_SUPPLY_PROP_PRESENT, 0x00, 0, 65535),
[REG_BATTERY_MODE] =
SBS_DATA(-1, 0x03, 0, 65535),
[REG_TEMPERATURE] = [REG_TEMPERATURE] =
SBS_DATA(POWER_SUPPLY_PROP_TEMP, 0x08, 0, 65535), SBS_DATA(POWER_SUPPLY_PROP_TEMP, 0x08, 0, 65535),
[REG_VOLTAGE] = [REG_VOLTAGE] =
SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_NOW, 0x09, 0, 20000), SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_NOW, 0x09, 0, 20000),
[REG_CURRENT] = [REG_CURRENT_NOW] =
SBS_DATA(POWER_SUPPLY_PROP_CURRENT_NOW, 0x0A, -32768, 32767), SBS_DATA(POWER_SUPPLY_PROP_CURRENT_NOW, 0x0A, -32768, 32767),
[REG_CURRENT_AVG] =
SBS_DATA(POWER_SUPPLY_PROP_CURRENT_AVG, 0x0B, -32768, 32767),
[REG_MAX_ERR] =
SBS_DATA(POWER_SUPPLY_PROP_CAPACITY_ERROR_MARGIN, 0x0c, 0, 100),
[REG_CAPACITY] = [REG_CAPACITY] =
SBS_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x0D, 0, 100), SBS_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x0D, 0, 100),
[REG_REMAINING_CAPACITY] = [REG_REMAINING_CAPACITY] =
...@@ -99,6 +114,10 @@ static const struct chip_data { ...@@ -99,6 +114,10 @@ static const struct chip_data {
SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, 0x12, 0, 65535), SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, 0x12, 0, 65535),
[REG_TIME_TO_FULL] = [REG_TIME_TO_FULL] =
SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, 0x13, 0, 65535), SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, 0x13, 0, 65535),
[REG_CHARGE_CURRENT] =
SBS_DATA(POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, 0x14, 0, 65535),
[REG_CHARGE_VOLTAGE] =
SBS_DATA(POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, 0x15, 0, 65535),
[REG_STATUS] = [REG_STATUS] =
SBS_DATA(POWER_SUPPLY_PROP_STATUS, 0x16, 0, 65535), SBS_DATA(POWER_SUPPLY_PROP_STATUS, 0x16, 0, 65535),
[REG_CAPACITY_LEVEL] = [REG_CAPACITY_LEVEL] =
...@@ -119,10 +138,12 @@ static const struct chip_data { ...@@ -119,10 +138,12 @@ static const struct chip_data {
[REG_MANUFACTURER] = [REG_MANUFACTURER] =
SBS_DATA(POWER_SUPPLY_PROP_MANUFACTURER, 0x20, 0, 65535), SBS_DATA(POWER_SUPPLY_PROP_MANUFACTURER, 0x20, 0, 65535),
[REG_MODEL_NAME] = [REG_MODEL_NAME] =
SBS_DATA(POWER_SUPPLY_PROP_MODEL_NAME, 0x21, 0, 65535) SBS_DATA(POWER_SUPPLY_PROP_MODEL_NAME, 0x21, 0, 65535),
[REG_CHEMISTRY] =
SBS_DATA(POWER_SUPPLY_PROP_TECHNOLOGY, 0x22, 0, 65535)
}; };
static enum power_supply_property sbs_properties[] = { static const enum power_supply_property sbs_properties[] = {
POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CAPACITY_LEVEL, POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_HEALTH,
...@@ -131,7 +152,9 @@ static enum power_supply_property sbs_properties[] = { ...@@ -131,7 +152,9 @@ static enum power_supply_property sbs_properties[] = {
POWER_SUPPLY_PROP_CYCLE_COUNT, POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CURRENT_AVG,
POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CAPACITY_ERROR_MARGIN,
POWER_SUPPLY_PROP_TEMP, POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
...@@ -144,13 +167,18 @@ static enum power_supply_property sbs_properties[] = { ...@@ -144,13 +167,18 @@ static enum power_supply_property sbs_properties[] = {
POWER_SUPPLY_PROP_CHARGE_NOW, POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL, POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
POWER_SUPPLY_PROP_MANUFACTURE_YEAR,
POWER_SUPPLY_PROP_MANUFACTURE_MONTH,
POWER_SUPPLY_PROP_MANUFACTURE_DAY,
/* Properties of type `const char *' */ /* Properties of type `const char *' */
POWER_SUPPLY_PROP_MANUFACTURER, POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_MODEL_NAME POWER_SUPPLY_PROP_MODEL_NAME
}; };
/* Supports special manufacturer commands from TI BQ20Z75 IC. */ /* Supports special manufacturer commands from TI BQ20Z65 and BQ20Z75 IC. */
#define SBS_FLAGS_TI_BQ20Z75 BIT(0) #define SBS_FLAGS_TI_BQ20ZX5 BIT(0)
struct sbs_info { struct sbs_info {
struct i2c_client *client; struct i2c_client *client;
...@@ -158,6 +186,7 @@ struct sbs_info { ...@@ -158,6 +186,7 @@ struct sbs_info {
bool is_present; bool is_present;
struct gpio_desc *gpio_detect; struct gpio_desc *gpio_detect;
bool enable_detection; bool enable_detection;
bool charger_broadcasts;
int last_state; int last_state;
int poll_time; int poll_time;
u32 i2c_retry_count; u32 i2c_retry_count;
...@@ -169,8 +198,48 @@ struct sbs_info { ...@@ -169,8 +198,48 @@ struct sbs_info {
static char model_name[I2C_SMBUS_BLOCK_MAX + 1]; static char model_name[I2C_SMBUS_BLOCK_MAX + 1];
static char manufacturer[I2C_SMBUS_BLOCK_MAX + 1]; static char manufacturer[I2C_SMBUS_BLOCK_MAX + 1];
static char chemistry[I2C_SMBUS_BLOCK_MAX + 1];
static bool force_load; static bool force_load;
static int sbs_read_word_data(struct i2c_client *client, u8 address);
static int sbs_write_word_data(struct i2c_client *client, u8 address, u16 value);
static void sbs_disable_charger_broadcasts(struct sbs_info *chip)
{
int val = sbs_read_word_data(chip->client, BATTERY_MODE_OFFSET);
if (val < 0)
goto exit;
val |= BATTERY_MODE_CHARGER_MASK;
val = sbs_write_word_data(chip->client, BATTERY_MODE_OFFSET, val);
exit:
if (val < 0)
dev_err(&chip->client->dev,
"Failed to disable charger broadcasting: %d\n", val);
else
dev_dbg(&chip->client->dev, "%s\n", __func__);
}
static int sbs_update_presence(struct sbs_info *chip, bool is_present)
{
if (chip->is_present == is_present)
return 0;
if (!is_present) {
chip->is_present = false;
return 0;
}
if (!chip->is_present && is_present && !chip->charger_broadcasts)
sbs_disable_charger_broadcasts(chip);
chip->is_present = true;
return 0;
}
static int sbs_read_word_data(struct i2c_client *client, u8 address) static int sbs_read_word_data(struct i2c_client *client, u8 address)
{ {
struct sbs_info *chip = i2c_get_clientdata(client); struct sbs_info *chip = i2c_get_clientdata(client);
...@@ -288,15 +357,15 @@ static int sbs_status_correct(struct i2c_client *client, int *intval) ...@@ -288,15 +357,15 @@ static int sbs_status_correct(struct i2c_client *client, int *intval)
{ {
int ret; int ret;
ret = sbs_read_word_data(client, sbs_data[REG_CURRENT].addr); ret = sbs_read_word_data(client, sbs_data[REG_CURRENT_NOW].addr);
if (ret < 0) if (ret < 0)
return ret; return ret;
ret = (s16)ret; ret = (s16)ret;
/* Not drawing current means full (cannot be not charging) */ /* Not drawing current -> not charging (i.e. idle) */
if (ret == 0) if (*intval != POWER_SUPPLY_STATUS_FULL && ret == 0)
*intval = POWER_SUPPLY_STATUS_FULL; *intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
if (*intval == POWER_SUPPLY_STATUS_FULL) { if (*intval == POWER_SUPPLY_STATUS_FULL) {
/* Drawing or providing current when full */ /* Drawing or providing current when full */
...@@ -309,6 +378,17 @@ static int sbs_status_correct(struct i2c_client *client, int *intval) ...@@ -309,6 +378,17 @@ static int sbs_status_correct(struct i2c_client *client, int *intval)
return 0; return 0;
} }
static bool sbs_bat_needs_calibration(struct i2c_client *client)
{
int ret;
ret = sbs_read_word_data(client, sbs_data[REG_BATTERY_MODE].addr);
if (ret < 0)
return false;
return !!(ret & BIT(7));
}
static int sbs_get_battery_presence_and_health( static int sbs_get_battery_presence_and_health(
struct i2c_client *client, enum power_supply_property psp, struct i2c_client *client, enum power_supply_property psp,
union power_supply_propval *val) union power_supply_propval *val)
...@@ -328,9 +408,14 @@ static int sbs_get_battery_presence_and_health( ...@@ -328,9 +408,14 @@ static int sbs_get_battery_presence_and_health(
if (psp == POWER_SUPPLY_PROP_PRESENT) if (psp == POWER_SUPPLY_PROP_PRESENT)
val->intval = 1; /* battery present */ val->intval = 1; /* battery present */
else /* POWER_SUPPLY_PROP_HEALTH */ else { /* POWER_SUPPLY_PROP_HEALTH */
/* SBS spec doesn't have a general health command. */ if (sbs_bat_needs_calibration(client)) {
val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; val->intval = POWER_SUPPLY_HEALTH_CALIBRATION_REQUIRED;
} else {
/* SBS spec doesn't have a general health command. */
val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
}
}
return 0; return 0;
} }
...@@ -384,6 +469,8 @@ static int sbs_get_ti_battery_presence_and_health( ...@@ -384,6 +469,8 @@ static int sbs_get_ti_battery_presence_and_health(
val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
else if (ret == 0x0C) else if (ret == 0x0C)
val->intval = POWER_SUPPLY_HEALTH_DEAD; val->intval = POWER_SUPPLY_HEALTH_DEAD;
else if (sbs_bat_needs_calibration(client))
val->intval = POWER_SUPPLY_HEALTH_CALIBRATION_REQUIRED;
else else
val->intval = POWER_SUPPLY_HEALTH_GOOD; val->intval = POWER_SUPPLY_HEALTH_GOOD;
} }
...@@ -492,7 +579,10 @@ static void sbs_unit_adjustment(struct i2c_client *client, ...@@ -492,7 +579,10 @@ static void sbs_unit_adjustment(struct i2c_client *client,
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
case POWER_SUPPLY_PROP_CURRENT_NOW: case POWER_SUPPLY_PROP_CURRENT_NOW:
case POWER_SUPPLY_PROP_CURRENT_AVG:
case POWER_SUPPLY_PROP_CHARGE_NOW: case POWER_SUPPLY_PROP_CHARGE_NOW:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
case POWER_SUPPLY_PROP_CHARGE_FULL: case POWER_SUPPLY_PROP_CHARGE_FULL:
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
val->intval *= BASE_UNIT_CONVERSION; val->intval *= BASE_UNIT_CONVERSION;
...@@ -602,6 +692,70 @@ static int sbs_get_property_index(struct i2c_client *client, ...@@ -602,6 +692,70 @@ static int sbs_get_property_index(struct i2c_client *client,
return -EINVAL; return -EINVAL;
} }
static int sbs_get_chemistry(struct i2c_client *client,
union power_supply_propval *val)
{
enum power_supply_property psp = POWER_SUPPLY_PROP_TECHNOLOGY;
int ret;
ret = sbs_get_property_index(client, psp);
if (ret < 0)
return ret;
ret = sbs_get_battery_string_property(client, ret, psp,
chemistry);
if (ret < 0)
return ret;
if (!strncasecmp(chemistry, "LION", 4))
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
else if (!strncasecmp(chemistry, "LiP", 3))
val->intval = POWER_SUPPLY_TECHNOLOGY_LIPO;
else if (!strncasecmp(chemistry, "NiCd", 4))
val->intval = POWER_SUPPLY_TECHNOLOGY_NiCd;
else if (!strncasecmp(chemistry, "NiMH", 4))
val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH;
else
val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
if (val->intval == POWER_SUPPLY_TECHNOLOGY_UNKNOWN)
dev_warn(&client->dev, "Unknown chemistry: %s\n", chemistry);
return 0;
}
static int sbs_get_battery_manufacture_date(struct i2c_client *client,
enum power_supply_property psp,
union power_supply_propval *val)
{
int ret;
u16 day, month, year;
ret = sbs_read_word_data(client, REG_ADDR_MANUFACTURE_DATE);
if (ret < 0)
return ret;
day = ret & GENMASK(4, 0);
month = (ret & GENMASK(8, 5)) >> 5;
year = ((ret & GENMASK(15, 9)) >> 9) + 1980;
switch (psp) {
case POWER_SUPPLY_PROP_MANUFACTURE_YEAR:
val->intval = year;
break;
case POWER_SUPPLY_PROP_MANUFACTURE_MONTH:
val->intval = month;
break;
case POWER_SUPPLY_PROP_MANUFACTURE_DAY:
val->intval = day;
break;
default:
return -EINVAL;
}
return 0;
}
static int sbs_get_property(struct power_supply *psy, static int sbs_get_property(struct power_supply *psy,
enum power_supply_property psp, enum power_supply_property psp,
union power_supply_propval *val) union power_supply_propval *val)
...@@ -616,7 +770,7 @@ static int sbs_get_property(struct power_supply *psy, ...@@ -616,7 +770,7 @@ static int sbs_get_property(struct power_supply *psy,
return ret; return ret;
if (psp == POWER_SUPPLY_PROP_PRESENT) { if (psp == POWER_SUPPLY_PROP_PRESENT) {
val->intval = ret; val->intval = ret;
chip->is_present = val->intval; sbs_update_presence(chip, ret);
return 0; return 0;
} }
if (ret == 0) if (ret == 0)
...@@ -626,7 +780,7 @@ static int sbs_get_property(struct power_supply *psy, ...@@ -626,7 +780,7 @@ static int sbs_get_property(struct power_supply *psy,
switch (psp) { switch (psp) {
case POWER_SUPPLY_PROP_PRESENT: case POWER_SUPPLY_PROP_PRESENT:
case POWER_SUPPLY_PROP_HEALTH: case POWER_SUPPLY_PROP_HEALTH:
if (chip->flags & SBS_FLAGS_TI_BQ20Z75) if (chip->flags & SBS_FLAGS_TI_BQ20ZX5)
ret = sbs_get_ti_battery_presence_and_health(client, ret = sbs_get_ti_battery_presence_and_health(client,
psp, val); psp, val);
else else
...@@ -639,7 +793,10 @@ static int sbs_get_property(struct power_supply *psy, ...@@ -639,7 +793,10 @@ static int sbs_get_property(struct power_supply *psy,
break; break;
case POWER_SUPPLY_PROP_TECHNOLOGY: case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LION; ret = sbs_get_chemistry(client, val);
if (ret < 0)
break;
goto done; /* don't trigger power_supply_changed()! */ goto done; /* don't trigger power_supply_changed()! */
case POWER_SUPPLY_PROP_ENERGY_NOW: case POWER_SUPPLY_PROP_ENERGY_NOW:
...@@ -670,12 +827,16 @@ static int sbs_get_property(struct power_supply *psy, ...@@ -670,12 +827,16 @@ static int sbs_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_CYCLE_COUNT: case POWER_SUPPLY_PROP_CYCLE_COUNT:
case POWER_SUPPLY_PROP_VOLTAGE_NOW: case POWER_SUPPLY_PROP_VOLTAGE_NOW:
case POWER_SUPPLY_PROP_CURRENT_NOW: case POWER_SUPPLY_PROP_CURRENT_NOW:
case POWER_SUPPLY_PROP_CURRENT_AVG:
case POWER_SUPPLY_PROP_TEMP: case POWER_SUPPLY_PROP_TEMP:
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG:
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
case POWER_SUPPLY_PROP_CAPACITY: case POWER_SUPPLY_PROP_CAPACITY:
case POWER_SUPPLY_PROP_CAPACITY_ERROR_MARGIN:
ret = sbs_get_property_index(client, psp); ret = sbs_get_property_index(client, psp);
if (ret < 0) if (ret < 0)
break; break;
...@@ -703,6 +864,12 @@ static int sbs_get_property(struct power_supply *psy, ...@@ -703,6 +864,12 @@ static int sbs_get_property(struct power_supply *psy,
val->strval = manufacturer; val->strval = manufacturer;
break; break;
case POWER_SUPPLY_PROP_MANUFACTURE_YEAR:
case POWER_SUPPLY_PROP_MANUFACTURE_MONTH:
case POWER_SUPPLY_PROP_MANUFACTURE_DAY:
ret = sbs_get_battery_manufacture_date(client, psp, val);
break;
default: default:
dev_err(&client->dev, dev_err(&client->dev,
"%s: INVALID property\n", __func__); "%s: INVALID property\n", __func__);
...@@ -714,7 +881,7 @@ static int sbs_get_property(struct power_supply *psy, ...@@ -714,7 +881,7 @@ static int sbs_get_property(struct power_supply *psy,
if (!chip->gpio_detect && if (!chip->gpio_detect &&
chip->is_present != (ret >= 0)) { chip->is_present != (ret >= 0)) {
chip->is_present = (ret >= 0); sbs_update_presence(chip, (ret >= 0));
power_supply_changed(chip->power_supply); power_supply_changed(chip->power_supply);
} }
...@@ -745,7 +912,7 @@ static void sbs_supply_changed(struct sbs_info *chip) ...@@ -745,7 +912,7 @@ static void sbs_supply_changed(struct sbs_info *chip)
ret = gpiod_get_value_cansleep(chip->gpio_detect); ret = gpiod_get_value_cansleep(chip->gpio_detect);
if (ret < 0) if (ret < 0)
return; return;
chip->is_present = ret; sbs_update_presence(chip, ret);
power_supply_changed(battery); power_supply_changed(battery);
} }
...@@ -815,8 +982,7 @@ static const struct power_supply_desc sbs_default_desc = { ...@@ -815,8 +982,7 @@ static const struct power_supply_desc sbs_default_desc = {
.external_power_changed = sbs_external_power_changed, .external_power_changed = sbs_external_power_changed,
}; };
static int sbs_probe(struct i2c_client *client, static int sbs_probe(struct i2c_client *client)
const struct i2c_device_id *id)
{ {
struct sbs_info *chip; struct sbs_info *chip;
struct power_supply_desc *sbs_desc; struct power_supply_desc *sbs_desc;
...@@ -839,7 +1005,7 @@ static int sbs_probe(struct i2c_client *client, ...@@ -839,7 +1005,7 @@ static int sbs_probe(struct i2c_client *client,
if (!chip) if (!chip)
return -ENOMEM; return -ENOMEM;
chip->flags = (u32)(uintptr_t)of_device_get_match_data(&client->dev); chip->flags = (u32)(uintptr_t)device_get_match_data(&client->dev);
chip->client = client; chip->client = client;
chip->enable_detection = false; chip->enable_detection = false;
psy_cfg.of_node = client->dev.of_node; psy_cfg.of_node = client->dev.of_node;
...@@ -850,13 +1016,13 @@ static int sbs_probe(struct i2c_client *client, ...@@ -850,13 +1016,13 @@ static int sbs_probe(struct i2c_client *client,
/* use pdata if available, fall back to DT properties, /* use pdata if available, fall back to DT properties,
* or hardcoded defaults if not * or hardcoded defaults if not
*/ */
rc = of_property_read_u32(client->dev.of_node, "sbs,i2c-retry-count", rc = device_property_read_u32(&client->dev, "sbs,i2c-retry-count",
&chip->i2c_retry_count); &chip->i2c_retry_count);
if (rc) if (rc)
chip->i2c_retry_count = 0; chip->i2c_retry_count = 0;
rc = of_property_read_u32(client->dev.of_node, "sbs,poll-retry-count", rc = device_property_read_u32(&client->dev, "sbs,poll-retry-count",
&chip->poll_retry_count); &chip->poll_retry_count);
if (rc) if (rc)
chip->poll_retry_count = 0; chip->poll_retry_count = 0;
...@@ -866,6 +1032,9 @@ static int sbs_probe(struct i2c_client *client, ...@@ -866,6 +1032,9 @@ static int sbs_probe(struct i2c_client *client,
} }
chip->i2c_retry_count = chip->i2c_retry_count + 1; chip->i2c_retry_count = chip->i2c_retry_count + 1;
chip->charger_broadcasts = !device_property_read_bool(&client->dev,
"sbs,disable-charger-broadcasts");
chip->gpio_detect = devm_gpiod_get_optional(&client->dev, chip->gpio_detect = devm_gpiod_get_optional(&client->dev,
"sbs,battery-detect", GPIOD_IN); "sbs,battery-detect", GPIOD_IN);
if (IS_ERR(chip->gpio_detect)) { if (IS_ERR(chip->gpio_detect)) {
...@@ -950,7 +1119,7 @@ static int sbs_suspend(struct device *dev) ...@@ -950,7 +1119,7 @@ static int sbs_suspend(struct device *dev)
if (chip->poll_time > 0) if (chip->poll_time > 0)
cancel_delayed_work_sync(&chip->work); cancel_delayed_work_sync(&chip->work);
if (chip->flags & SBS_FLAGS_TI_BQ20Z75) { if (chip->flags & SBS_FLAGS_TI_BQ20ZX5) {
/* Write to manufacturer access with sleep command. */ /* Write to manufacturer access with sleep command. */
ret = sbs_write_word_data(client, ret = sbs_write_word_data(client,
sbs_data[REG_MANUFACTURER_DATA].addr, sbs_data[REG_MANUFACTURER_DATA].addr,
...@@ -970,6 +1139,7 @@ static SIMPLE_DEV_PM_OPS(sbs_pm_ops, sbs_suspend, NULL); ...@@ -970,6 +1139,7 @@ static SIMPLE_DEV_PM_OPS(sbs_pm_ops, sbs_suspend, NULL);
#endif #endif
static const struct i2c_device_id sbs_id[] = { static const struct i2c_device_id sbs_id[] = {
{ "bq20z65", 0 },
{ "bq20z75", 0 }, { "bq20z75", 0 },
{ "sbs-battery", 1 }, { "sbs-battery", 1 },
{} {}
...@@ -978,16 +1148,20 @@ MODULE_DEVICE_TABLE(i2c, sbs_id); ...@@ -978,16 +1148,20 @@ MODULE_DEVICE_TABLE(i2c, sbs_id);
static const struct of_device_id sbs_dt_ids[] = { static const struct of_device_id sbs_dt_ids[] = {
{ .compatible = "sbs,sbs-battery" }, { .compatible = "sbs,sbs-battery" },
{
.compatible = "ti,bq20z65",
.data = (void *)SBS_FLAGS_TI_BQ20ZX5,
},
{ {
.compatible = "ti,bq20z75", .compatible = "ti,bq20z75",
.data = (void *)SBS_FLAGS_TI_BQ20Z75, .data = (void *)SBS_FLAGS_TI_BQ20ZX5,
}, },
{ } { }
}; };
MODULE_DEVICE_TABLE(of, sbs_dt_ids); MODULE_DEVICE_TABLE(of, sbs_dt_ids);
static struct i2c_driver sbs_battery_driver = { static struct i2c_driver sbs_battery_driver = {
.probe = sbs_probe, .probe_new = sbs_probe,
.remove = sbs_remove, .remove = sbs_remove,
.alert = sbs_alert, .alert = sbs_alert,
.id_table = sbs_id, .id_table = sbs_id,
......
...@@ -42,6 +42,8 @@ ...@@ -42,6 +42,8 @@
#define SC27XX_FGU_USER_AREA_SET 0xa0 #define SC27XX_FGU_USER_AREA_SET 0xa0
#define SC27XX_FGU_USER_AREA_CLEAR 0xa4 #define SC27XX_FGU_USER_AREA_CLEAR 0xa4
#define SC27XX_FGU_USER_AREA_STATUS 0xa8 #define SC27XX_FGU_USER_AREA_STATUS 0xa8
#define SC27XX_FGU_VOLTAGE_BUF 0xd0
#define SC27XX_FGU_CURRENT_BUF 0xf0
#define SC27XX_WRITE_SELCLB_EN BIT(0) #define SC27XX_WRITE_SELCLB_EN BIT(0)
#define SC27XX_FGU_CLBCNT_MASK GENMASK(15, 0) #define SC27XX_FGU_CLBCNT_MASK GENMASK(15, 0)
...@@ -82,6 +84,7 @@ ...@@ -82,6 +84,7 @@
* @init_clbcnt: the initial coulomb counter * @init_clbcnt: the initial coulomb counter
* @max_volt: the maximum constant input voltage in millivolt * @max_volt: the maximum constant input voltage in millivolt
* @min_volt: the minimum drained battery voltage in microvolt * @min_volt: the minimum drained battery voltage in microvolt
* @boot_volt: the voltage measured during boot in microvolt
* @table_len: the capacity table length * @table_len: the capacity table length
* @resist_table_len: the resistance table length * @resist_table_len: the resistance table length
* @cur_1000ma_adc: ADC value corresponding to 1000 mA * @cur_1000ma_adc: ADC value corresponding to 1000 mA
...@@ -107,6 +110,7 @@ struct sc27xx_fgu_data { ...@@ -107,6 +110,7 @@ struct sc27xx_fgu_data {
int init_clbcnt; int init_clbcnt;
int max_volt; int max_volt;
int min_volt; int min_volt;
int boot_volt;
int table_len; int table_len;
int resist_table_len; int resist_table_len;
int cur_1000ma_adc; int cur_1000ma_adc;
...@@ -319,6 +323,7 @@ static int sc27xx_fgu_get_boot_capacity(struct sc27xx_fgu_data *data, int *cap) ...@@ -319,6 +323,7 @@ static int sc27xx_fgu_get_boot_capacity(struct sc27xx_fgu_data *data, int *cap)
volt = sc27xx_fgu_adc_to_voltage(data, volt); volt = sc27xx_fgu_adc_to_voltage(data, volt);
ocv = volt * 1000 - oci * data->internal_resist; ocv = volt * 1000 - oci * data->internal_resist;
data->boot_volt = ocv;
/* /*
* Parse the capacity table to look up the correct capacity percent * Parse the capacity table to look up the correct capacity percent
...@@ -376,6 +381,44 @@ static int sc27xx_fgu_get_clbcnt(struct sc27xx_fgu_data *data, int *clb_cnt) ...@@ -376,6 +381,44 @@ static int sc27xx_fgu_get_clbcnt(struct sc27xx_fgu_data *data, int *clb_cnt)
return 0; return 0;
} }
static int sc27xx_fgu_get_vol_now(struct sc27xx_fgu_data *data, int *val)
{
int ret;
u32 vol;
ret = regmap_read(data->regmap, data->base + SC27XX_FGU_VOLTAGE_BUF,
&vol);
if (ret)
return ret;
/*
* It is ADC values reading from registers which need to convert to
* corresponding voltage values.
*/
*val = sc27xx_fgu_adc_to_voltage(data, vol);
return 0;
}
static int sc27xx_fgu_get_cur_now(struct sc27xx_fgu_data *data, int *val)
{
int ret;
u32 cur;
ret = regmap_read(data->regmap, data->base + SC27XX_FGU_CURRENT_BUF,
&cur);
if (ret)
return ret;
/*
* It is ADC values reading from registers which need to convert to
* corresponding current values.
*/
*val = sc27xx_fgu_adc_to_current(data, cur - SC27XX_FGU_CUR_BASIC_ADC);
return 0;
}
static int sc27xx_fgu_get_capacity(struct sc27xx_fgu_data *data, int *cap) static int sc27xx_fgu_get_capacity(struct sc27xx_fgu_data *data, int *cap)
{ {
int ret, cur_clbcnt, delta_clbcnt, delta_cap, temp; int ret, cur_clbcnt, delta_clbcnt, delta_cap, temp;
...@@ -577,7 +620,7 @@ static int sc27xx_fgu_get_property(struct power_supply *psy, ...@@ -577,7 +620,7 @@ static int sc27xx_fgu_get_property(struct power_supply *psy,
val->intval = value; val->intval = value;
break; break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW: case POWER_SUPPLY_PROP_VOLTAGE_AVG:
ret = sc27xx_fgu_get_vbat_vol(data, &value); ret = sc27xx_fgu_get_vbat_vol(data, &value);
if (ret) if (ret)
goto error; goto error;
...@@ -601,7 +644,6 @@ static int sc27xx_fgu_get_property(struct power_supply *psy, ...@@ -601,7 +644,6 @@ static int sc27xx_fgu_get_property(struct power_supply *psy,
val->intval = value; val->intval = value;
break; break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
case POWER_SUPPLY_PROP_CURRENT_AVG: case POWER_SUPPLY_PROP_CURRENT_AVG:
ret = sc27xx_fgu_get_current(data, &value); ret = sc27xx_fgu_get_current(data, &value);
if (ret) if (ret)
...@@ -625,6 +667,26 @@ static int sc27xx_fgu_get_property(struct power_supply *psy, ...@@ -625,6 +667,26 @@ static int sc27xx_fgu_get_property(struct power_supply *psy,
break; break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
ret = sc27xx_fgu_get_vol_now(data, &value);
if (ret)
goto error;
val->intval = value * 1000;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
ret = sc27xx_fgu_get_cur_now(data, &value);
if (ret)
goto error;
val->intval = value * 1000;
break;
case POWER_SUPPLY_PROP_VOLTAGE_BOOT:
val->intval = data->boot_volt;
break;
default: default:
ret = -EINVAL; ret = -EINVAL;
break; break;
...@@ -656,6 +718,11 @@ static int sc27xx_fgu_set_property(struct power_supply *psy, ...@@ -656,6 +718,11 @@ static int sc27xx_fgu_set_property(struct power_supply *psy,
ret = 0; ret = 0;
break; break;
case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
data->total_cap = val->intval / 1000;
ret = 0;
break;
default: default:
ret = -EINVAL; ret = -EINVAL;
} }
...@@ -676,7 +743,8 @@ static int sc27xx_fgu_property_is_writeable(struct power_supply *psy, ...@@ -676,7 +743,8 @@ static int sc27xx_fgu_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp) enum power_supply_property psp)
{ {
return psp == POWER_SUPPLY_PROP_CAPACITY || return psp == POWER_SUPPLY_PROP_CAPACITY ||
psp == POWER_SUPPLY_PROP_CALIBRATE; psp == POWER_SUPPLY_PROP_CALIBRATE ||
psp == POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN;
} }
static enum power_supply_property sc27xx_fgu_props[] = { static enum power_supply_property sc27xx_fgu_props[] = {
...@@ -688,6 +756,8 @@ static enum power_supply_property sc27xx_fgu_props[] = { ...@@ -688,6 +756,8 @@ static enum power_supply_property sc27xx_fgu_props[] = {
POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_OCV, POWER_SUPPLY_PROP_VOLTAGE_OCV,
POWER_SUPPLY_PROP_VOLTAGE_AVG,
POWER_SUPPLY_PROP_VOLTAGE_BOOT,
POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CURRENT_AVG, POWER_SUPPLY_PROP_CURRENT_AVG,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
...@@ -705,6 +775,7 @@ static const struct power_supply_desc sc27xx_fgu_desc = { ...@@ -705,6 +775,7 @@ static const struct power_supply_desc sc27xx_fgu_desc = {
.set_property = sc27xx_fgu_set_property, .set_property = sc27xx_fgu_set_property,
.external_power_changed = sc27xx_fgu_external_power_changed, .external_power_changed = sc27xx_fgu_external_power_changed,
.property_is_writeable = sc27xx_fgu_property_is_writeable, .property_is_writeable = sc27xx_fgu_property_is_writeable,
.no_thermal = true,
}; };
static void sc27xx_fgu_adjust_cap(struct sc27xx_fgu_data *data, int cap) static void sc27xx_fgu_adjust_cap(struct sc27xx_fgu_data *data, int cap)
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
* Mika Westerberg <mika.westerberg@linux.intel.com> * Mika Westerberg <mika.westerberg@linux.intel.com>
*/ */
#include <linux/delay.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/gpio.h> #include <linux/gpio.h>
#include <linux/kernel.h> #include <linux/kernel.h>
...@@ -708,6 +709,9 @@ static irqreturn_t smb347_interrupt(int irq, void *data) ...@@ -708,6 +709,9 @@ static irqreturn_t smb347_interrupt(int irq, void *data)
bool handled = false; bool handled = false;
int ret; int ret;
/* SMB347 it needs at least 20ms for setting IRQSTAT_E_*IN_UV_IRQ */
usleep_range(25000, 35000);
ret = regmap_read(smb->regmap, STAT_C, &stat_c); ret = regmap_read(smb->regmap, STAT_C, &stat_c);
if (ret < 0) { if (ret < 0) {
dev_warn(smb->dev, "reading STAT_C failed\n"); dev_warn(smb->dev, "reading STAT_C failed\n");
...@@ -1138,6 +1142,7 @@ static bool smb347_volatile_reg(struct device *dev, unsigned int reg) ...@@ -1138,6 +1142,7 @@ static bool smb347_volatile_reg(struct device *dev, unsigned int reg)
switch (reg) { switch (reg) {
case IRQSTAT_A: case IRQSTAT_A:
case IRQSTAT_C: case IRQSTAT_C:
case IRQSTAT_D:
case IRQSTAT_E: case IRQSTAT_E:
case IRQSTAT_F: case IRQSTAT_F:
case STAT_A: case STAT_A:
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
#include <linux/uidgid.h> #include <linux/uidgid.h>
#define UEVENT_HELPER_PATH_LEN 256 #define UEVENT_HELPER_PATH_LEN 256
#define UEVENT_NUM_ENVP 32 /* number of env pointers */ #define UEVENT_NUM_ENVP 64 /* number of env pointers */
#define UEVENT_BUFFER_SIZE 2048 /* buffer for the variables */ #define UEVENT_BUFFER_SIZE 2048 /* buffer for the variables */
#ifdef CONFIG_UEVENT_HELPER #ifdef CONFIG_UEVENT_HELPER
......
...@@ -61,6 +61,7 @@ enum { ...@@ -61,6 +61,7 @@ enum {
POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE, POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE,
POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE, POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE,
POWER_SUPPLY_HEALTH_OVERCURRENT, POWER_SUPPLY_HEALTH_OVERCURRENT,
POWER_SUPPLY_HEALTH_CALIBRATION_REQUIRED,
}; };
enum { enum {
...@@ -139,6 +140,7 @@ enum power_supply_property { ...@@ -139,6 +140,7 @@ enum power_supply_property {
POWER_SUPPLY_PROP_CAPACITY, /* in percents! */ POWER_SUPPLY_PROP_CAPACITY, /* in percents! */
POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN, /* in percents! */ POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN, /* in percents! */
POWER_SUPPLY_PROP_CAPACITY_ALERT_MAX, /* in percents! */ POWER_SUPPLY_PROP_CAPACITY_ALERT_MAX, /* in percents! */
POWER_SUPPLY_PROP_CAPACITY_ERROR_MARGIN, /* in percents! */
POWER_SUPPLY_PROP_CAPACITY_LEVEL, POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_TEMP, POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TEMP_MAX, POWER_SUPPLY_PROP_TEMP_MAX,
...@@ -158,6 +160,9 @@ enum power_supply_property { ...@@ -158,6 +160,9 @@ enum power_supply_property {
POWER_SUPPLY_PROP_PRECHARGE_CURRENT, POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
POWER_SUPPLY_PROP_CALIBRATE, POWER_SUPPLY_PROP_CALIBRATE,
POWER_SUPPLY_PROP_MANUFACTURE_YEAR,
POWER_SUPPLY_PROP_MANUFACTURE_MONTH,
POWER_SUPPLY_PROP_MANUFACTURE_DAY,
/* Properties of type `const char *' */ /* Properties of type `const char *' */
POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER, POWER_SUPPLY_PROP_MANUFACTURER,
...@@ -223,9 +228,9 @@ struct power_supply_config { ...@@ -223,9 +228,9 @@ struct power_supply_config {
struct power_supply_desc { struct power_supply_desc {
const char *name; const char *name;
enum power_supply_type type; enum power_supply_type type;
enum power_supply_usb_type *usb_types; const enum power_supply_usb_type *usb_types;
size_t num_usb_types; size_t num_usb_types;
enum power_supply_property *properties; const enum power_supply_property *properties;
size_t num_properties; size_t num_properties;
/* /*
...@@ -346,8 +351,12 @@ struct power_supply_battery_info { ...@@ -346,8 +351,12 @@ struct power_supply_battery_info {
int charge_full_design_uah; /* microAmp-hours */ int charge_full_design_uah; /* microAmp-hours */
int voltage_min_design_uv; /* microVolts */ int voltage_min_design_uv; /* microVolts */
int voltage_max_design_uv; /* microVolts */ int voltage_max_design_uv; /* microVolts */
int tricklecharge_current_ua; /* microAmps */
int precharge_current_ua; /* microAmps */ int precharge_current_ua; /* microAmps */
int precharge_voltage_max_uv; /* microVolts */
int charge_term_current_ua; /* microAmps */ int charge_term_current_ua; /* microAmps */
int charge_restart_voltage_uv; /* microVolts */
int overvoltage_limit_uv; /* microVolts */
int constant_charge_current_max_ua; /* microAmps */ int constant_charge_current_max_ua; /* microAmps */
int constant_charge_voltage_max_uv; /* microVolts */ int constant_charge_voltage_max_uv; /* microVolts */
int factory_internal_resistance_uohm; /* microOhms */ int factory_internal_resistance_uohm; /* microOhms */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment