Commit 38525c69 authored by Linus Torvalds's avatar Linus Torvalds

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

Pull power supply and reset updates from Sebastian Reichel:
 "Power-supply core:
   - add wireless type
   - properly document current direction

  Battery/charger driver changes:
   - new fuel-gauge/charger driver for RN5T618/RN5T619
   - new charger driver for BQ25980, BQ25975 and BQ25960
   - bq27xxx-battery: add support for TI bq34z100
   - gpio-charger: convert to GPIO descriptors
   - gpio-charger: add optional support for charge current limiting
   - max17040: add support for max17041, max17043, max17044
   - max17040: add support for max17048, max17049, max17058, max17059
   - smb347-charger: add DT support
   - smb247-charger: add SMB345 and SMB358 support
   - simple-battery: add temperature properties
   - lots of minor fixes, cleanups and DT binding YAML conversions

  Reset drivers:
   - ocelot: Add support for Sparx5"

* tag 'for-v5.10' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (81 commits)
  power: reset: POWER_RESET_OCELOT_RESET should depend on Ocelot or Sparx5
  power: supply: bq25980: Fix uninitialized wd_reg_val and overrun
  power: supply: ltc2941: Fix ptr to enum cast
  power: supply: test-power: revise parameter printing to use sprintf
  power: supply: charger-manager: fix incorrect check on charging_duration_ms
  power: supply: max17040: Fix ptr to enum cast
  power: supply: bq25980: Fix uninitialized wd_reg_val
  power: supply: bq25980: remove redundant zero check on ret
  power: reset: ocelot: Add support for Sparx5
  dt-bindings: reset: ocelot: Add Sparx5 support
  power: supply: sbs-battery: keep error code when get_property() fails
  power: supply: bq25980: Add support for the BQ259xx family
  dt-binding: bq25980: Add the bq25980 flash charger
  power: supply: fix spelling mistake "unprecise" -> "imprecise"
  power: supply: test_power: add missing newlines when printing parameters by sysfs
  power: supply: pm2301: drop duplicated i2c_device_id
  power: supply: charger-manager: drop unused charger assignment
  power: supply: rt9455: skip 'struct acpi_device_id' when !CONFIG_ACPI
  power: supply: goldfish: skip 'struct acpi_device_id' when !CONFIG_ACPI
  power: supply: bq25890: skip 'struct acpi_device_id' when !CONFIG_ACPI
  ...
parents f9915b96 7007fab4
......@@ -34,7 +34,7 @@ Description:
Describes the main type of the supply.
Access: Read
Valid values: "Battery", "UPS", "Mains", "USB"
Valid values: "Battery", "UPS", "Mains", "USB", "Wireless"
===== Battery Properties =====
......@@ -108,7 +108,8 @@ Description:
which they average readings to smooth out the reported value.
Access: Read
Valid values: Represented in microamps
Valid values: Represented in microamps. Negative values are used
for discharging batteries, positive values for charging batteries.
What: /sys/class/power_supply/<supply_name>/current_max
Date: October 2010
......@@ -127,7 +128,8 @@ Description:
This value is not averaged/smoothed.
Access: Read
Valid values: Represented in microamps
Valid values: Represented in microamps. Negative values are used
for discharging batteries, positive values for charging batteries.
What: /sys/class/power_supply/<supply_name>/charge_control_limit
Date: Oct 2012
......
Microsemi Ocelot reset controller
The DEVCPU_GCB:CHIP_REGS have a SOFT_RST register that can be used to reset the
SoC MIPS core.
SoC core.
The reset registers are both present in the MSCC vcoreiii MIPS and
microchip Sparx5 armv8 SoC's.
Required Properties:
- compatible: "mscc,ocelot-chip-reset"
- compatible: "mscc,ocelot-chip-reset" or "microchip,sparx5-chip-reset"
Example:
reset@1070008 {
......
Generic reboot mode core map driver
This driver get reboot mode arguments and call the write
interface to store the magic value in special register
or ram. Then the bootloader can read it and take different
action according to the argument stored.
All mode properties are vendor specific, it is a indication to tell
the bootloader what to do when the system reboots, and should be named
as mode-xxx = <magic> (xxx is mode name, magic should be a none-zero value).
For example modes common on Android platform:
- mode-normal: Normal reboot mode, system reboot with command "reboot".
- mode-recovery: Android Recovery mode, it is a mode to format the device or update a new image.
- mode-bootloader: Android fastboot mode, it's a mode to re-flash partitions on the Android based device.
- mode-loader: A bootloader mode, it's a mode used to download image on Rockchip platform,
usually used in development.
Example:
reboot-mode {
mode-normal = <BOOT_NORMAL>;
mode-recovery = <BOOT_RECOVERY>;
mode-bootloader = <BOOT_FASTBOOT>;
mode-loader = <BOOT_BL_DOWNLOAD>;
}
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/power/reset/reboot-mode.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Generic reboot mode core map
maintainers:
- Andy Yan <andy.yan@rock-chips.com>
description: |
This driver get reboot mode arguments and call the write
interface to store the magic value in special register
or ram. Then the bootloader can read it and take different
action according to the argument stored.
All mode properties are vendor specific, it is a indication to tell
the bootloader what to do when the system reboots, and should be named
as mode-xxx = <magic> (xxx is mode name, magic should be a non-zero value).
For example, modes common Android platform are:
- normal: Normal reboot mode, system reboot with command "reboot".
- recovery: Android Recovery mode, it is a mode to format the device or update a new image.
- bootloader: Android fastboot mode, it's a mode to re-flash partitions on the Android based device.
- loader: A bootloader mode, it's a mode used to download image on Rockchip platform,
usually used in development.
properties:
mode-normal:
$ref: /schemas/types.yaml#/definitions/uint32
description: |
Default value to set on a reboot if no command was provided.
patternProperties:
"^mode-.*$":
$ref: /schemas/types.yaml#/definitions/uint32
examples:
- |
reboot-mode {
mode-normal = <0>;
mode-recovery = <1>;
mode-bootloader = <2>;
mode-loader = <3>;
};
...
......@@ -82,6 +82,27 @@ properties:
An array containing the temperature in degree Celsius,
for each of the battery capacity lookup table.
operating-range-celsius:
$ref: /schemas/types.yaml#/definitions/uint32-array
description: operating temperature range of a battery
items:
- description: minimum temperature at which battery can operate
- description: maximum temperature at which battery can operate
ambient-celsius:
$ref: /schemas/types.yaml#/definitions/uint32-array
description: safe range of ambient temperature
items:
- description: alert when ambient temperature is lower than this value
- description: alert when ambient temperature is higher than this value
alert-celsius:
$ref: /schemas/types.yaml#/definitions/uint32-array
description: safe range of battery temperature
items:
- description: alert when battery temperature is lower than this value
- description: alert when battery temperature is higher than this value
required:
- compatible
......@@ -130,6 +151,9 @@ examples:
/* table for 10 degree Celsius */
ocv-capacity-table-2 = <4250000 100>, <4200000 95>, <4185000 90>;
resistance-temp-table = <20 100>, <10 90>, <0 80>, <(-10) 60>;
operating-range-celsius = <(-30) 50>;
ambient-celsius = <(-5) 50>;
alert-celsius = <0 40>;
};
charger@11 {
......
......@@ -33,6 +33,10 @@ Optional properties:
- ti,thermal-regulation-threshold: integer, temperature above which the charge
current is lowered, to avoid overheating (in degrees Celsius). If omitted,
the default setting will be used (120 degrees);
- ti,ibatcomp-micro-ohms: integer, value of a resistor in series with
the battery;
- ti,ibatcomp-clamp-microvolt: integer, maximum charging voltage adjustment due
to expected voltage drop on in-series resistor;
Example:
......
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
# Copyright (C) 2020 Texas Instruments Incorporated
%YAML 1.2
---
$id: "http://devicetree.org/schemas/power/supply/bq25980.yaml#"
$schema: "http://devicetree.org/meta-schemas/core.yaml#"
title: TI BQ25980 Flash Charger
maintainers:
- Dan Murphy <dmurphy@ti.com>
- Ricardo Rivera-Matos <r-rivera-matos@ti.com>
description: |
The BQ25980, BQ25975, and BQ25960 are a series of flash chargers intended
for use in high-power density portable electronics. These inductorless
switching chargers can provide over 97% efficiency by making use of the
switched capacitor architecture.
allOf:
- $ref: power-supply.yaml#
properties:
compatible:
enum:
- ti,bq25980
- ti,bq25975
- ti,bq25960
reg:
maxItems: 1
ti,watchdog-timeout-ms:
description: |
Watchdog timer in milli seconds. 0 disables the watchdog.
default: 0
minimum: 0
maximum: 300000
enum: [ 0, 5000, 10000, 50000, 300000]
ti,sc-ovp-limit-microvolt:
description: |
Minimum input voltage limit in micro volts with a when the charger is in
switch cap mode. 100000 micro volt step.
default: 17800000
minimum: 14000000
maximum: 22000000
ti,sc-ocp-limit-microamp:
description: |
Maximum input current limit in micro amps with a 100000 micro amp step.
minimum: 100000
maximum: 3300000
ti,bypass-ovp-limit-microvolt:
description: |
Minimum input voltage limit in micro volts with a when the charger is in
switch cap mode. 50000 micro volt step.
minimum: 7000000
maximum: 12750000
ti,bypass-ocp-limit-microamp:
description: |
Maximum input current limit in micro amps with a 100000 micro amp step.
minimum: 100000
maximum: 3300000
ti,bypass-enable:
type: boolean
description: Enables bypass mode at boot time
interrupts:
description: |
Indicates that the device state has changed.
monitored-battery:
$ref: /schemas/types.yaml#/definitions/phandle
description: phandle to the battery node being monitored
required:
- compatible
- reg
- monitored-battery
unevaluatedProperties: false
examples:
- |
bat: battery {
compatible = "simple-battery";
constant-charge-current-max-microamp = <4000000>;
constant-charge-voltage-max-microvolt = <8400000>;
precharge-current-microamp = <160000>;
charge-term-current-microamp = <160000>;
};
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
i2c0 {
#address-cells = <1>;
#size-cells = <0>;
bq25980: charger@65 {
compatible = "ti,bq25980";
reg = <0x65>;
interrupt-parent = <&gpio1>;
interrupts = <16 IRQ_TYPE_EDGE_FALLING>;
ti,watchdog-timer = <0>;
ti,sc-ocp-limit-microamp = <2000000>;
ti,sc-ovp-limit-microvolt = <17800000>;
monitored-battery = <&bat>;
};
};
...
......@@ -51,6 +51,7 @@ properties:
- ti,bq27621
- ti,bq27z561
- ti,bq28z610
- ti,bq34z100
reg:
maxItems: 1
......
......@@ -3,24 +3,32 @@ charger-manager bindings
Required properties :
- compatible : "charger-manager"
- <>-supply : for regulator consumer
- cm-num-chargers : number of chargers
- <>-supply : for regulator consumer, named according to cm-regulator-name
- cm-chargers : name of chargers
- cm-fuel-gauge : name of battery fuel gauge
- subnode <regulator> :
- cm-regulator-name : name of charger regulator
- subnode <cable> :
- cm-cable-name : name of charger cable
- cm-cable-name : name of charger cable - one of USB, USB-HOST,
SDP, DCP, CDP, ACA, FAST-CHARGER, SLOW-CHARGER, WPT,
PD, DOCK, JIG, or MECHANICAL
- cm-cable-extcon : name of extcon dev
(optional) - cm-cable-min : minimum current of cable
(optional) - cm-cable-max : maximum current of cable
Optional properties :
- cm-name : charger manager's name (default : "battery")
- cm-poll-mode : polling mode (enum polling_modes)
- cm-poll-interval : polling interval
- cm-battery-stat : battery status (enum data_source)
- cm-fullbatt-* : data for full battery checking
- cm-poll-mode : polling mode - 0 for disabled, 1 for always, 2 for when
external power is connected, or 3 for when charging. If not present,
then polling is disabled
- cm-poll-interval : polling interval (in ms)
- cm-battery-stat : battery status - 0 for battery always present, 1 for no
battery, 2 to check presence via fuel gauge, or 3 to check presence
via charger
- cm-fullbatt-vchkdrop-volt : voltage drop (in uV) before restarting charging
- cm-fullbatt-voltage : voltage (in uV) of full battery
- cm-fullbatt-soc : state of charge to consider as full battery
- cm-fullbatt-capacity : capcity (in uAh) to consider as full battery
- cm-thermal-zone : name of external thermometer's thermal zone
- cm-battery-* : threshold battery temperature for charging
-cold : critical cold temperature of battery for charging
......@@ -29,6 +37,10 @@ Optional properties :
-temp-diff : temperature difference to allow recharging
- cm-dis/charging-max = limits of charging duration
Deprecated properties:
- cm-num-chargers
- cm-fullbatt-vchkdrop-ms
Example :
charger-manager@0 {
compatible = "charger-manager";
......@@ -39,13 +51,11 @@ Example :
cm-poll-mode = <1>;
cm-poll-interval = <30000>;
cm-fullbatt-vchkdrop-ms = <30000>;
cm-fullbatt-vchkdrop-volt = <150000>;
cm-fullbatt-soc = <100>;
cm-battery-stat = <3>;
cm-num-chargers = <3>;
cm-chargers = "charger0", "charger1", "charger2";
cm-fuel-gauge = "fuelgauge0";
......@@ -71,7 +81,7 @@ Example :
cm-cable-max = <500000>;
};
cable@1 {
cm-cable-name = "TA";
cm-cable-name = "SDP";
cm-cable-extcon = "extcon-dev.0";
cm-cable-min = <650000>;
cm-cable-max = <675000>;
......
......@@ -39,6 +39,25 @@ properties:
maxItems: 1
description: GPIO indicating the charging status
charge-current-limit-gpios:
minItems: 1
maxItems: 32
description: GPIOs used for current limiting
charge-current-limit-mapping:
description: List of tuples with current in uA and a GPIO bitmap (in
this order). The tuples must be provided in descending order of the
current limit.
$ref: /schemas/types.yaml#/definitions/uint32-matrix
items:
items:
- description:
Current limit in uA
- description:
Encoded GPIO setting. Bit 0 represents last GPIO from the
charge-current-limit-gpios property. Bit 1 second to last
GPIO and so on.
required:
- compatible
......@@ -47,6 +66,12 @@ anyOf:
- gpios
- required:
- charge-status-gpios
- required:
- charge-current-limit-gpios
dependencies:
charge-current-limit-gpios: [ charge-current-limit-mapping ]
charge-current-limit-mapping: [ charge-current-limit-gpios ]
additionalProperties: false
......@@ -60,4 +85,10 @@ examples:
gpios = <&gpd 28 GPIO_ACTIVE_LOW>;
charge-status-gpios = <&gpc 27 GPIO_ACTIVE_LOW>;
charge-current-limit-gpios = <&gpioA 11 GPIO_ACTIVE_HIGH>,
<&gpioA 12 GPIO_ACTIVE_HIGH>;
charge-current-limit-mapping = <2500000 0x00>, // 2.5 A => both GPIOs low
<700000 0x01>, // 700 mA => GPIO A.12 high
<0 0x02>; // 0 mA => GPIO A.11 high
};
* Ingenic JZ47xx battery bindings
Required properties:
- compatible: Must be "ingenic,jz4740-battery".
- io-channels: phandle and IIO specifier pair to the IIO device.
Format described in iio-bindings.txt.
- monitored-battery: phandle to a "simple-battery" compatible node.
The "monitored-battery" property must be a phandle to a node using the format
described in battery.txt, with the following properties being required:
- voltage-min-design-microvolt: Drained battery voltage.
- voltage-max-design-microvolt: Fully charged battery voltage.
Example:
#include <dt-bindings/iio/adc/ingenic,adc.h>
simple_battery: battery {
compatible = "simple-battery";
voltage-min-design-microvolt = <3600000>;
voltage-max-design-microvolt = <4200000>;
};
ingenic_battery {
compatible = "ingenic,jz4740-battery";
io-channels = <&adc INGENIC_ADC_BATTERY>;
io-channel-names = "battery";
monitored-battery = <&simple_battery>;
};
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
# Copyright 2019-2020 Artur Rojek
%YAML 1.2
---
$id: "http://devicetree.org/schemas/power/supply/ingenic,battery.yaml#"
$schema: "http://devicetree.org/meta-schemas/core.yaml#"
title: Ingenic JZ47xx battery bindings
maintainers:
- Artur Rojek <contact@artur-rojek.eu>
properties:
compatible:
oneOf:
- const: ingenic,jz4740-battery
- items:
- enum:
- ingenic,jz4725b-battery
- ingenic,jz4770-battery
- const: ingenic,jz4740-battery
io-channels:
maxItems: 1
io-channel-names:
const: battery
monitored-battery:
description: >
phandle to a "simple-battery" compatible node.
This property must be a phandle to a node using the format described
in battery.yaml, with the following properties being required:
- voltage-min-design-microvolt: drained battery voltage,
- voltage-max-design-microvolt: fully charged battery voltage.
required:
- compatible
- io-channels
- io-channel-names
- monitored-battery
additionalProperties: false
examples:
- |
#include <dt-bindings/iio/adc/ingenic,adc.h>
simple_battery: battery {
compatible = "simple-battery";
voltage-min-design-microvolt = <3600000>;
voltage-max-design-microvolt = <4200000>;
};
ingenic-battery {
compatible = "ingenic,jz4740-battery";
io-channels = <&adc INGENIC_ADC_BATTERY>;
io-channel-names = "battery";
monitored-battery = <&simple_battery>;
};
......@@ -2,7 +2,9 @@ max17040_battery
~~~~~~~~~~~~~~~~
Required properties :
- compatible : "maxim,max17040" or "maxim,max77836-battery"
- compatible : "maxim,max17040", "maxim,max17041", "maxim,max17043",
"maxim,max17044", "maxim,max17048", "maxim,max17049",
"maxim,max17058", "maxim,max17059" or "maxim,max77836-battery"
- reg: i2c slave address
Optional properties :
......@@ -11,6 +13,15 @@ Optional properties :
generated. Can be configured from 1 up to 32
(%). If skipped the power up default value of
4 (%) will be used.
- maxim,double-soc : Certain devices return double the capacity.
Specify this boolean property to divide the
reported value in 2 and thus normalize it.
SOC == State of Charge == Capacity.
- maxim,rcomp : A value to compensate readings for various
battery chemistries and operating temperatures.
max17040,41 have 2 byte rcomp, default to
0x97 0x00. All other devices have one byte
rcomp, default to 0x97.
- interrupts : Interrupt line see Documentation/devicetree/
bindings/interrupt-controller/interrupts.txt
- wakeup-source : This device has wakeup capabilities. Use this
......@@ -31,3 +42,11 @@ Example:
interrupts = <2 IRQ_TYPE_EDGE_FALLING>;
wakeup-source;
};
battery-fuel-gauge@36 {
compatible = "maxim,max17048";
reg = <0x36>;
maxim,rcomp = /bits/ 8 <0x56>;
maxim,alert-low-soc-level = <10>;
maxim,double-soc;
};
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: "http://devicetree.org/schemas/power/supply/summit,smb347-charger.yaml#"
$schema: "http://devicetree.org/meta-schemas/core.yaml#"
title: Battery charger driver for SMB345, SMB347 and SMB358
maintainers:
- David Heidelberg <david@ixit.cz>
- Dmitry Osipenko <digetx@gmail.com>
properties:
compatible:
enum:
- summit,smb345
- summit,smb347
- summit,smb358
reg:
maxItems: 1
interrupts:
maxItems: 1
monitored-battery:
description: phandle to the battery node
$ref: /schemas/types.yaml#/definitions/phandle
summit,enable-usb-charging:
type: boolean
description: Enable charging through USB.
summit,enable-otg-charging:
type: boolean
description: Provide power for USB OTG
summit,enable-mains-charging:
type: boolean
description: Enable charging through mains
summit,enable-charge-control:
description: Enable charging control
$ref: /schemas/types.yaml#/definitions/uint32
enum:
- 0 # SMB3XX_CHG_ENABLE_SW SW (I2C interface)
- 1 # SMB3XX_CHG_ENABLE_PIN_ACTIVE_LOW Pin control (Active Low)
- 2 # SMB3XX_CHG_ENABLE_PIN_ACTIVE_HIGH Pin control (Active High)
summit,fast-voltage-threshold-microvolt:
description: Voltage threshold to transit to fast charge mode (in uV)
minimum: 2400000
maximum: 3000000
summit,mains-current-limit-microamp:
description: Maximum input current from AC/DC input (in uA)
summit,usb-current-limit-microamp:
description: Maximum input current from USB input (in uA)
summit,charge-current-compensation-microamp:
description: Charge current compensation (in uA)
summit,chip-temperature-threshold-celsius:
description: Chip temperature for thermal regulation in °C.
enum: [100, 110, 120, 130]
summit,soft-compensation-method:
description: Soft temperature limit compensation method
$ref: /schemas/types.yaml#/definitions/uint32
enum:
- 0 # SMB3XX_SOFT_TEMP_COMPENSATE_NONE Compensation none
- 1 # SMB3XX_SOFT_TEMP_COMPENSATE_CURRENT Current compensation
- 2 # SMB3XX_SOFT_TEMP_COMPENSATE_VOLTAGE Voltage compensation
allOf:
- if:
properties:
compatible:
enum:
- summit,smb345
- summit,smb358
then:
properties:
summit,mains-current-limit-microamp:
enum: [ 300000, 500000, 700000, 1000000,
1500000, 1800000, 2000000]
summit,usb-current-limit-microamp:
enum: [ 300000, 500000, 700000, 1000000,
1500000, 1800000, 2000000]
summit,charge-current-compensation-microamp:
enum: [200000, 450000, 600000, 900000]
else:
properties:
summit,mains-current-limit-microamp:
enum: [ 300000, 500000, 700000, 900000, 1200000,
1500000, 1800000, 2000000, 2200000, 2500000]
summit,usb-current-limit-microamp:
enum: [ 300000, 500000, 700000, 900000, 1200000,
1500000, 1800000, 2000000, 2200000, 2500000]
summit,charge-current-compensation-microamp:
enum: [250000, 700000, 900000, 1200000]
required:
- compatible
- reg
anyOf:
- required:
- summit,enable-usb-charging
- required:
- summit,enable-otg-charging
- required:
- summit,enable-mains-charging
additionalProperties: false
examples:
- |
#include <dt-bindings/power/summit,smb347-charger.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
charger@7f {
compatible = "summit,smb347";
reg = <0x7f>;
summit,enable-charge-control = <SMB3XX_CHG_ENABLE_PIN_ACTIVE_HIGH>;
summit,chip-temperature-threshold-celsius = <110>;
summit,mains-current-limit-microamp = <2000000>;
summit,usb-current-limit-microamp = <500000>;
summit,enable-usb-charging;
summit,enable-mains-charging;
monitored-battery = <&battery>;
};
};
battery: battery-cell {
compatible = "simple-battery";
constant-charge-current-max-microamp = <1800000>;
operating-range-celsius = <0 45>;
alert-celsius = <3 42>;
};
......@@ -5231,7 +5231,6 @@ F: kernel/dma/
DMA-BUF HEAPS FRAMEWORK
M: Sumit Semwal <sumit.semwal@linaro.org>
R: Andrew F. Davis <afd@ti.com>
R: Benjamin Gaignard <benjamin.gaignard@linaro.org>
R: Liam Mark <lmark@codeaurora.org>
R: Laura Abbott <labbott@redhat.com>
......@@ -11605,6 +11604,7 @@ M: Microchip Linux Driver Support <UNGLinuxDriver@microchip.com>
L: linux-mips@vger.kernel.org
S: Supported
F: Documentation/devicetree/bindings/mips/mscc.txt
F: Documentation/devicetree/bindings/power/reset/ocelot-reset.txt
F: arch/mips/boot/dts/mscc/
F: arch/mips/configs/generic/board-ocelot.config
F: arch/mips/generic/board-ocelot.c
......@@ -17391,7 +17391,7 @@ S: Maintained
F: drivers/thermal/ti-soc-thermal/
TI BQ27XXX POWER SUPPLY DRIVER
R: Andrew F. Davis <afd@ti.com>
R: Dan Murphy <dmurphy@ti.com>
F: drivers/power/supply/bq27xxx_battery.c
F: drivers/power/supply/bq27xxx_battery_i2c.c
F: include/linux/power/bq27xxx_battery.h
......
......@@ -369,6 +369,15 @@ static struct pxaficp_platform_data tosa_ficp_platform_data = {
/*
* Tosa AC IN
*/
static struct gpiod_lookup_table tosa_power_gpiod_table = {
.dev_id = "gpio-charger",
.table = {
GPIO_LOOKUP("gpio-pxa", TOSA_GPIO_AC_IN,
NULL, GPIO_ACTIVE_LOW),
{ },
},
};
static char *tosa_ac_supplied_to[] = {
"main-battery",
"backup-battery",
......@@ -378,8 +387,6 @@ static char *tosa_ac_supplied_to[] = {
static struct gpio_charger_platform_data tosa_power_data = {
.name = "charger",
.type = POWER_SUPPLY_TYPE_MAINS,
.gpio = TOSA_GPIO_AC_IN,
.gpio_active_low = 1,
.supplied_to = tosa_ac_supplied_to,
.num_supplicants = ARRAY_SIZE(tosa_ac_supplied_to),
};
......@@ -951,6 +958,7 @@ static void __init tosa_init(void)
clk_add_alias("CLK_CK3P6MI", tc6393xb_device.name, "GPIO11_CLK", NULL);
gpiod_add_lookup_table(&tosa_udc_gpiod_table);
gpiod_add_lookup_table(&tosa_power_gpiod_table);
platform_add_devices(devices, ARRAY_SIZE(devices));
}
......
......@@ -30,6 +30,7 @@
#include <linux/gpio_keys.h>
#include <linux/input.h>
#include <linux/gpio.h>
#include <linux/gpio/machine.h>
#include <linux/power/gpio-charger.h>
#include <video/sa1100fb.h>
......@@ -131,16 +132,23 @@ static struct irda_platform_data collie_ir_data = {
/*
* Collie AC IN
*/
static struct gpiod_lookup_table collie_power_gpiod_table = {
.dev_id = "gpio-charger",
.table = {
GPIO_LOOKUP("gpio", COLLIE_GPIO_AC_IN,
NULL, GPIO_ACTIVE_HIGH),
{ },
},
};
static char *collie_ac_supplied_to[] = {
"main-battery",
"backup-battery",
};
static struct gpio_charger_platform_data collie_power_data = {
.name = "charger",
.type = POWER_SUPPLY_TYPE_MAINS,
.gpio = COLLIE_GPIO_AC_IN,
.supplied_to = collie_ac_supplied_to,
.num_supplicants = ARRAY_SIZE(collie_ac_supplied_to),
};
......@@ -386,6 +394,8 @@ static void __init collie_init(void)
platform_scoop_config = &collie_pcmcia_config;
gpiod_add_lookup_table(&collie_power_gpiod_table);
ret = platform_add_devices(devices, ARRAY_SIZE(devices));
if (ret) {
printk(KERN_WARNING "collie: Unable to register LoCoMo device\n");
......
......@@ -129,10 +129,10 @@ config POWER_RESET_QCOM_PON
config POWER_RESET_OCELOT_RESET
bool "Microsemi Ocelot reset driver"
depends on MSCC_OCELOT || COMPILE_TEST
depends on MSCC_OCELOT || ARCH_SPARX5 || COMPILE_TEST
select MFD_SYSCON
help
This driver supports restart for Microsemi Ocelot SoC.
This driver supports restart for Microsemi Ocelot SoC and similar.
config POWER_RESET_OXNAS
bool "OXNAS SoC restart driver"
......
......@@ -15,15 +15,20 @@
#include <linux/reboot.h>
#include <linux/regmap.h>
struct reset_props {
const char *syscon;
u32 protect_reg;
u32 vcore_protect;
u32 if_si_owner_bit;
};
struct ocelot_reset_context {
void __iomem *base;
struct regmap *cpu_ctrl;
const struct reset_props *props;
struct notifier_block restart_handler;
};
#define ICPU_CFG_CPU_SYSTEM_CTRL_RESET 0x20
#define CORE_RST_PROTECT BIT(2)
#define SOFT_CHIP_RST BIT(0)
#define ICPU_CFG_CPU_SYSTEM_CTRL_GENERAL_CTRL 0x24
......@@ -31,7 +36,6 @@ struct ocelot_reset_context {
#define IF_SI_OWNER_SISL 0
#define IF_SI_OWNER_SIBM 1
#define IF_SI_OWNER_SIMC 2
#define IF_SI_OWNER_OFFSET 4
static int ocelot_restart_handle(struct notifier_block *this,
unsigned long mode, void *cmd)
......@@ -39,15 +43,18 @@ static int ocelot_restart_handle(struct notifier_block *this,
struct ocelot_reset_context *ctx = container_of(this, struct
ocelot_reset_context,
restart_handler);
u32 if_si_owner_bit = ctx->props->if_si_owner_bit;
/* Make sure the core is not protected from reset */
regmap_update_bits(ctx->cpu_ctrl, ICPU_CFG_CPU_SYSTEM_CTRL_RESET,
CORE_RST_PROTECT, 0);
regmap_update_bits(ctx->cpu_ctrl, ctx->props->protect_reg,
ctx->props->vcore_protect, 0);
/* Make the SI back to boot mode */
regmap_update_bits(ctx->cpu_ctrl, ICPU_CFG_CPU_SYSTEM_CTRL_GENERAL_CTRL,
IF_SI_OWNER_MASK << IF_SI_OWNER_OFFSET,
IF_SI_OWNER_SIBM << IF_SI_OWNER_OFFSET);
IF_SI_OWNER_MASK << if_si_owner_bit,
IF_SI_OWNER_SIBM << if_si_owner_bit);
pr_emerg("Resetting SoC\n");
writel(SOFT_CHIP_RST, ctx->base);
......@@ -72,9 +79,13 @@ static int ocelot_reset_probe(struct platform_device *pdev)
if (IS_ERR(ctx->base))
return PTR_ERR(ctx->base);
ctx->cpu_ctrl = syscon_regmap_lookup_by_compatible("mscc,ocelot-cpu-syscon");
if (IS_ERR(ctx->cpu_ctrl))
ctx->props = device_get_match_data(dev);
ctx->cpu_ctrl = syscon_regmap_lookup_by_compatible(ctx->props->syscon);
if (IS_ERR(ctx->cpu_ctrl)) {
dev_err(dev, "No syscon map: %s\n", ctx->props->syscon);
return PTR_ERR(ctx->cpu_ctrl);
}
ctx->restart_handler.notifier_call = ocelot_restart_handle;
ctx->restart_handler.priority = 192;
......@@ -85,9 +96,29 @@ static int ocelot_reset_probe(struct platform_device *pdev)
return err;
}
static const struct reset_props reset_props_ocelot = {
.syscon = "mscc,ocelot-cpu-syscon",
.protect_reg = 0x20,
.vcore_protect = BIT(2),
.if_si_owner_bit = 4,
};
static const struct reset_props reset_props_sparx5 = {
.syscon = "microchip,sparx5-cpu-syscon",
.protect_reg = 0x84,
.vcore_protect = BIT(10),
.if_si_owner_bit = 6,
};
static const struct of_device_id ocelot_reset_of_match[] = {
{ .compatible = "mscc,ocelot-chip-reset" },
{}
{
.compatible = "mscc,ocelot-chip-reset",
.data = &reset_props_ocelot
}, {
.compatible = "microchip,sparx5-chip-reset",
.data = &reset_props_sparx5
},
{ /*sentinel*/ }
};
static struct platform_driver ocelot_reset_driver = {
......
......@@ -164,7 +164,7 @@ config BATTERY_DS2782
config BATTERY_LEGO_EV3
tristate "LEGO MINDSTORMS EV3 battery"
depends on OF && IIO && GPIOLIB
depends on OF && IIO && GPIOLIB && (ARCH_DAVINCI_DA850 || COMPILE_TEST)
help
Say Y here to enable support for the LEGO MINDSTORMS EV3 battery.
......@@ -367,10 +367,15 @@ config AXP288_FUEL_GAUGE
config BATTERY_MAX17040
tristate "Maxim MAX17040 Fuel Gauge"
depends on I2C
select REGMAP_I2C
help
MAX17040 is fuel-gauge systems for lithium-ion (Li+) batteries
in handheld and portable equipment. The MAX17040 is configured
to operate with a single lithium cell
Maxim models with ModelGauge are fuel-gauge systems for lithium-ion
(Li+) batteries in handheld and portable equipment, including
max17040, max17041, max17043, max17044, max17048, max17049, max17058,
max17059. It is also included in some batteries like max77836.
Driver supports reporting SOC (State of Charge, i.e capacity),
voltage and configurable low-SOC wakeup interrupt.
config BATTERY_MAX17042
tristate "Maxim MAX17042/17047/17050/8997/8966 Fuel Gauge"
......@@ -631,13 +636,22 @@ config CHARGER_BQ25890
help
Say Y to enable support for the TI BQ25890 battery charger.
config CHARGER_BQ25980
tristate "TI BQ25980 battery charger driver"
depends on I2C
depends on GPIOLIB || COMPILE_TEST
select REGMAP_I2C
help
Say Y to enable support for the TI BQ25980, BQ25975 and BQ25960
series of fast battery chargers.
config CHARGER_SMB347
tristate "Summit Microelectronics SMB347 Battery Charger"
tristate "Summit Microelectronics SMB3XX Battery Charger"
depends on I2C
select REGMAP_I2C
help
Say Y to include support for Summit Microelectronics SMB347
Battery Charger.
Say Y to include support for Summit Microelectronics SMB345,
SMB347 or SMB358 Battery Charger.
config CHARGER_TPS65090
tristate "TPS65090 battery charger driver"
......@@ -752,4 +766,12 @@ config CHARGER_WILCO
information can be found in
Documentation/ABI/testing/sysfs-class-power-wilco
config RN5T618_POWER
tristate "RN5T618 charger/fuel gauge support"
depends on MFD_RN5T618
help
Say Y here to have support for RN5T618 PMIC family fuel gauge and charger.
This driver can also be built as a module. If so, the module will be
called rn5t618_power.
endif # POWER_SUPPLY
......@@ -84,6 +84,7 @@ obj-$(CONFIG_CHARGER_BQ24257) += bq24257_charger.o
obj-$(CONFIG_CHARGER_BQ24735) += bq24735-charger.o
obj-$(CONFIG_CHARGER_BQ2515X) += bq2515x_charger.o
obj-$(CONFIG_CHARGER_BQ25890) += bq25890_charger.o
obj-$(CONFIG_CHARGER_BQ25980) += bq25980_charger.o
obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o
obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o
obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o
......@@ -96,3 +97,4 @@ obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o
obj-$(CONFIG_CHARGER_BD70528) += bd70528-charger.o
obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o
obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o
obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o
......@@ -653,7 +653,7 @@ int ab8500_fg_inst_curr_finalize(struct ab8500_fg *di, int *res)
/*
* negative value for Discharging
* convert 2's compliment into decimal
* convert 2's complement into decimal
*/
if (high & 0x10)
val = (low | (high << 8) | 0xFFFFE000);
......@@ -781,7 +781,7 @@ static void ab8500_fg_acc_cur_work(struct work_struct *work)
if (ret < 0)
goto exit;
/* Check for sign bit in case of negative value, 2's compliment */
/* Check for sign bit in case of negative value, 2's complement */
if (high & 0x10)
val = (low | (med << 8) | (high << 16) | 0xFFE00000);
else
......
......@@ -1152,6 +1152,7 @@ static const struct of_device_id bq24257_of_match[] = {
};
MODULE_DEVICE_TABLE(of, bq24257_of_match);
#ifdef CONFIG_ACPI
static const struct acpi_device_id bq24257_acpi_match[] = {
{ "BQ242500", BQ24250 },
{ "BQ242510", BQ24251 },
......@@ -1159,6 +1160,7 @@ static const struct acpi_device_id bq24257_acpi_match[] = {
{},
};
MODULE_DEVICE_TABLE(acpi, bq24257_acpi_match);
#endif
static struct i2c_driver bq24257_driver = {
.driver = {
......
......@@ -168,7 +168,7 @@ enum bq2515x_id {
* @device_id: value of device_id
* @mains_online: boolean value indicating power supply online
*
* @bq2515x_init_data init_data: charger initialization data structure
* @init_data: charger initialization data structure
*/
struct bq2515x_device {
struct power_supply *mains;
......@@ -188,7 +188,7 @@ struct bq2515x_device {
struct bq2515x_init_data init_data;
};
static struct reg_default bq25150_reg_defaults[] = {
static const struct reg_default bq25150_reg_defaults[] = {
{BQ2515X_FLAG0, 0x0},
{BQ2515X_FLAG1, 0x0},
{BQ2515X_FLAG2, 0x0},
......@@ -227,7 +227,7 @@ static struct reg_default bq25150_reg_defaults[] = {
{BQ2515X_DEVICE_ID, 0x20},
};
static struct reg_default bq25155_reg_defaults[] = {
static const struct reg_default bq25155_reg_defaults[] = {
{BQ2515X_FLAG0, 0x0},
{BQ2515X_FLAG1, 0x0},
{BQ2515X_FLAG2, 0x0},
......@@ -886,14 +886,14 @@ static int bq2515x_battery_get_property(struct power_supply *psy,
return 0;
}
static enum power_supply_property bq2515x_battery_properties[] = {
static const enum power_supply_property bq2515x_battery_properties[] = {
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
};
static enum power_supply_property bq2515x_mains_properties[] = {
static const enum power_supply_property bq2515x_mains_properties[] = {
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_HEALTH,
......@@ -905,7 +905,7 @@ static enum power_supply_property bq2515x_mains_properties[] = {
POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
};
static struct power_supply_desc bq2515x_mains_desc = {
static const struct power_supply_desc bq2515x_mains_desc = {
.name = "bq2515x-mains",
.type = POWER_SUPPLY_TYPE_MAINS,
.get_property = bq2515x_mains_get_property,
......@@ -915,7 +915,7 @@ static struct power_supply_desc bq2515x_mains_desc = {
.property_is_writeable = bq2515x_power_supply_property_is_writeable,
};
static struct power_supply_desc bq2515x_battery_desc = {
static const struct power_supply_desc bq2515x_battery_desc = {
.name = "bq2515x-battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.get_property = bq2515x_battery_get_property,
......
......@@ -83,6 +83,8 @@ struct bq25890_init_data {
u8 boostf; /* boost frequency */
u8 ilim_en; /* enable ILIM pin */
u8 treg; /* thermal regulation threshold */
u8 rbatcomp; /* IBAT sense resistor value */
u8 vclamp; /* IBAT compensation voltage limit */
};
struct bq25890_state {
......@@ -258,6 +260,8 @@ enum bq25890_table_ids {
TBL_VREG,
TBL_BOOSTV,
TBL_SYSVMIN,
TBL_VBATCOMP,
TBL_RBATCOMP,
/* lookup tables */
TBL_TREG,
......@@ -299,6 +303,8 @@ static const union {
[TBL_VREG] = { .rt = {3840000, 4608000, 16000} }, /* uV */
[TBL_BOOSTV] = { .rt = {4550000, 5510000, 64000} }, /* uV */
[TBL_SYSVMIN] = { .rt = {3000000, 3700000, 100000} }, /* uV */
[TBL_VBATCOMP] ={ .rt = {0, 224000, 32000} }, /* uV */
[TBL_RBATCOMP] ={ .rt = {0, 140000, 20000} }, /* uOhm */
/* lookup tables */
[TBL_TREG] = { .lt = {bq25890_treg_tbl, BQ25890_TREG_TBL_SIZE} },
......@@ -648,7 +654,9 @@ static int bq25890_hw_init(struct bq25890_device *bq)
{F_BOOSTI, bq->init_data.boosti},
{F_BOOSTF, bq->init_data.boostf},
{F_EN_ILIM, bq->init_data.ilim_en},
{F_TREG, bq->init_data.treg}
{F_TREG, bq->init_data.treg},
{F_BATCMP, bq->init_data.rbatcomp},
{F_VCLAMP, bq->init_data.vclamp},
};
ret = bq25890_chip_reset(bq);
......@@ -859,11 +867,14 @@ static int bq25890_fw_read_u32_props(struct bq25890_device *bq)
{"ti,boost-max-current", false, TBL_BOOSTI, &init->boosti},
/* optional properties */
{"ti,thermal-regulation-threshold", true, TBL_TREG, &init->treg}
{"ti,thermal-regulation-threshold", true, TBL_TREG, &init->treg},
{"ti,ibatcomp-micro-ohms", true, TBL_RBATCOMP, &init->rbatcomp},
{"ti,ibatcomp-clamp-microvolt", true, TBL_VBATCOMP, &init->vclamp},
};
/* initialize data for optional properties */
init->treg = 3; /* 120 degrees Celsius */
init->rbatcomp = init->vclamp = 0; /* IBAT compensation disabled */
for (i = 0; i < ARRAY_SIZE(props); i++) {
ret = device_property_read_u32(bq->dev, props[i].name,
......@@ -1073,11 +1084,13 @@ static const struct of_device_id bq25890_of_match[] = {
};
MODULE_DEVICE_TABLE(of, bq25890_of_match);
#ifdef CONFIG_ACPI
static const struct acpi_device_id bq25890_acpi_match[] = {
{"BQ258900", 0},
{},
};
MODULE_DEVICE_TABLE(acpi, bq25890_acpi_match);
#endif
static struct i2c_driver bq25890_driver = {
.driver = {
......
// SPDX-License-Identifier: GPL-2.0
// BQ25980 Battery Charger Driver
// Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com/
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/gpio/consumer.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include "bq25980_charger.h"
struct bq25980_state {
bool dischg;
bool ovp;
bool ocp;
bool wdt;
bool tflt;
bool online;
bool ce;
bool hiz;
bool bypass;
u32 vbat_adc;
u32 vsys_adc;
u32 ibat_adc;
};
enum bq25980_id {
BQ25980,
BQ25975,
BQ25960,
};
struct bq25980_chip_info {
int model_id;
const struct regmap_config *regmap_config;
int busocp_def;
int busocp_sc_max;
int busocp_byp_max;
int busocp_sc_min;
int busocp_byp_min;
int busovp_sc_def;
int busovp_byp_def;
int busovp_sc_step;
int busovp_sc_offset;
int busovp_byp_step;
int busovp_byp_offset;
int busovp_sc_min;
int busovp_sc_max;
int busovp_byp_min;
int busovp_byp_max;
int batovp_def;
int batovp_max;
int batovp_min;
int batovp_step;
int batovp_offset;
int batocp_def;
int batocp_max;
};
struct bq25980_init_data {
u32 ichg;
u32 bypass_ilim;
u32 sc_ilim;
u32 vreg;
u32 iterm;
u32 iprechg;
u32 bypass_vlim;
u32 sc_vlim;
u32 ichg_max;
u32 vreg_max;
};
struct bq25980_device {
struct i2c_client *client;
struct device *dev;
struct power_supply *charger;
struct power_supply *battery;
struct mutex lock;
struct regmap *regmap;
char model_name[I2C_NAME_SIZE];
struct bq25980_init_data init_data;
const struct bq25980_chip_info *chip_info;
struct bq25980_state state;
int watchdog_timer;
};
static struct reg_default bq25980_reg_defs[] = {
{BQ25980_BATOVP, 0x5A},
{BQ25980_BATOVP_ALM, 0x46},
{BQ25980_BATOCP, 0x51},
{BQ25980_BATOCP_ALM, 0x50},
{BQ25980_BATUCP_ALM, 0x28},
{BQ25980_CHRGR_CTRL_1, 0x0},
{BQ25980_BUSOVP, 0x26},
{BQ25980_BUSOVP_ALM, 0x22},
{BQ25980_BUSOCP, 0xD},
{BQ25980_BUSOCP_ALM, 0xC},
{BQ25980_TEMP_CONTROL, 0x30},
{BQ25980_TDIE_ALM, 0xC8},
{BQ25980_TSBUS_FLT, 0x15},
{BQ25980_TSBAT_FLG, 0x15},
{BQ25980_VAC_CONTROL, 0x0},
{BQ25980_CHRGR_CTRL_2, 0x0},
{BQ25980_CHRGR_CTRL_3, 0x20},
{BQ25980_CHRGR_CTRL_4, 0x1D},
{BQ25980_CHRGR_CTRL_5, 0x18},
{BQ25980_STAT1, 0x0},
{BQ25980_STAT2, 0x0},
{BQ25980_STAT3, 0x0},
{BQ25980_STAT4, 0x0},
{BQ25980_STAT5, 0x0},
{BQ25980_FLAG1, 0x0},
{BQ25980_FLAG2, 0x0},
{BQ25980_FLAG3, 0x0},
{BQ25980_FLAG4, 0x0},
{BQ25980_FLAG5, 0x0},
{BQ25980_MASK1, 0x0},
{BQ25980_MASK2, 0x0},
{BQ25980_MASK3, 0x0},
{BQ25980_MASK4, 0x0},
{BQ25980_MASK5, 0x0},
{BQ25980_DEVICE_INFO, 0x8},
{BQ25980_ADC_CONTROL1, 0x0},
{BQ25980_ADC_CONTROL2, 0x0},
{BQ25980_IBUS_ADC_LSB, 0x0},
{BQ25980_IBUS_ADC_MSB, 0x0},
{BQ25980_VBUS_ADC_LSB, 0x0},
{BQ25980_VBUS_ADC_MSB, 0x0},
{BQ25980_VAC1_ADC_LSB, 0x0},
{BQ25980_VAC2_ADC_LSB, 0x0},
{BQ25980_VOUT_ADC_LSB, 0x0},
{BQ25980_VBAT_ADC_LSB, 0x0},
{BQ25980_IBAT_ADC_MSB, 0x0},
{BQ25980_IBAT_ADC_LSB, 0x0},
{BQ25980_TSBUS_ADC_LSB, 0x0},
{BQ25980_TSBAT_ADC_LSB, 0x0},
{BQ25980_TDIE_ADC_LSB, 0x0},
{BQ25980_DEGLITCH_TIME, 0x0},
{BQ25980_CHRGR_CTRL_6, 0x0},
};
static struct reg_default bq25975_reg_defs[] = {
{BQ25980_BATOVP, 0x5A},
{BQ25980_BATOVP_ALM, 0x46},
{BQ25980_BATOCP, 0x51},
{BQ25980_BATOCP_ALM, 0x50},
{BQ25980_BATUCP_ALM, 0x28},
{BQ25980_CHRGR_CTRL_1, 0x0},
{BQ25980_BUSOVP, 0x26},
{BQ25980_BUSOVP_ALM, 0x22},
{BQ25980_BUSOCP, 0xD},
{BQ25980_BUSOCP_ALM, 0xC},
{BQ25980_TEMP_CONTROL, 0x30},
{BQ25980_TDIE_ALM, 0xC8},
{BQ25980_TSBUS_FLT, 0x15},
{BQ25980_TSBAT_FLG, 0x15},
{BQ25980_VAC_CONTROL, 0x0},
{BQ25980_CHRGR_CTRL_2, 0x0},
{BQ25980_CHRGR_CTRL_3, 0x20},
{BQ25980_CHRGR_CTRL_4, 0x1D},
{BQ25980_CHRGR_CTRL_5, 0x18},
{BQ25980_STAT1, 0x0},
{BQ25980_STAT2, 0x0},
{BQ25980_STAT3, 0x0},
{BQ25980_STAT4, 0x0},
{BQ25980_STAT5, 0x0},
{BQ25980_FLAG1, 0x0},
{BQ25980_FLAG2, 0x0},
{BQ25980_FLAG3, 0x0},
{BQ25980_FLAG4, 0x0},
{BQ25980_FLAG5, 0x0},
{BQ25980_MASK1, 0x0},
{BQ25980_MASK2, 0x0},
{BQ25980_MASK3, 0x0},
{BQ25980_MASK4, 0x0},
{BQ25980_MASK5, 0x0},
{BQ25980_DEVICE_INFO, 0x8},
{BQ25980_ADC_CONTROL1, 0x0},
{BQ25980_ADC_CONTROL2, 0x0},
{BQ25980_IBUS_ADC_LSB, 0x0},
{BQ25980_IBUS_ADC_MSB, 0x0},
{BQ25980_VBUS_ADC_LSB, 0x0},
{BQ25980_VBUS_ADC_MSB, 0x0},
{BQ25980_VAC1_ADC_LSB, 0x0},
{BQ25980_VAC2_ADC_LSB, 0x0},
{BQ25980_VOUT_ADC_LSB, 0x0},
{BQ25980_VBAT_ADC_LSB, 0x0},
{BQ25980_IBAT_ADC_MSB, 0x0},
{BQ25980_IBAT_ADC_LSB, 0x0},
{BQ25980_TSBUS_ADC_LSB, 0x0},
{BQ25980_TSBAT_ADC_LSB, 0x0},
{BQ25980_TDIE_ADC_LSB, 0x0},
{BQ25980_DEGLITCH_TIME, 0x0},
{BQ25980_CHRGR_CTRL_6, 0x0},
};
static struct reg_default bq25960_reg_defs[] = {
{BQ25980_BATOVP, 0x5A},
{BQ25980_BATOVP_ALM, 0x46},
{BQ25980_BATOCP, 0x51},
{BQ25980_BATOCP_ALM, 0x50},
{BQ25980_BATUCP_ALM, 0x28},
{BQ25980_CHRGR_CTRL_1, 0x0},
{BQ25980_BUSOVP, 0x26},
{BQ25980_BUSOVP_ALM, 0x22},
{BQ25980_BUSOCP, 0xD},
{BQ25980_BUSOCP_ALM, 0xC},
{BQ25980_TEMP_CONTROL, 0x30},
{BQ25980_TDIE_ALM, 0xC8},
{BQ25980_TSBUS_FLT, 0x15},
{BQ25980_TSBAT_FLG, 0x15},
{BQ25980_VAC_CONTROL, 0x0},
{BQ25980_CHRGR_CTRL_2, 0x0},
{BQ25980_CHRGR_CTRL_3, 0x20},
{BQ25980_CHRGR_CTRL_4, 0x1D},
{BQ25980_CHRGR_CTRL_5, 0x18},
{BQ25980_STAT1, 0x0},
{BQ25980_STAT2, 0x0},
{BQ25980_STAT3, 0x0},
{BQ25980_STAT4, 0x0},
{BQ25980_STAT5, 0x0},
{BQ25980_FLAG1, 0x0},
{BQ25980_FLAG2, 0x0},
{BQ25980_FLAG3, 0x0},
{BQ25980_FLAG4, 0x0},
{BQ25980_FLAG5, 0x0},
{BQ25980_MASK1, 0x0},
{BQ25980_MASK2, 0x0},
{BQ25980_MASK3, 0x0},
{BQ25980_MASK4, 0x0},
{BQ25980_MASK5, 0x0},
{BQ25980_DEVICE_INFO, 0x8},
{BQ25980_ADC_CONTROL1, 0x0},
{BQ25980_ADC_CONTROL2, 0x0},
{BQ25980_IBUS_ADC_LSB, 0x0},
{BQ25980_IBUS_ADC_MSB, 0x0},
{BQ25980_VBUS_ADC_LSB, 0x0},
{BQ25980_VBUS_ADC_MSB, 0x0},
{BQ25980_VAC1_ADC_LSB, 0x0},
{BQ25980_VAC2_ADC_LSB, 0x0},
{BQ25980_VOUT_ADC_LSB, 0x0},
{BQ25980_VBAT_ADC_LSB, 0x0},
{BQ25980_IBAT_ADC_MSB, 0x0},
{BQ25980_IBAT_ADC_LSB, 0x0},
{BQ25980_TSBUS_ADC_LSB, 0x0},
{BQ25980_TSBAT_ADC_LSB, 0x0},
{BQ25980_TDIE_ADC_LSB, 0x0},
{BQ25980_DEGLITCH_TIME, 0x0},
{BQ25980_CHRGR_CTRL_6, 0x0},
};
static int bq25980_watchdog_time[BQ25980_NUM_WD_VAL] = {5000, 10000, 50000,
300000};
static int bq25980_get_input_curr_lim(struct bq25980_device *bq)
{
unsigned int busocp_reg_code;
int ret;
ret = regmap_read(bq->regmap, BQ25980_BUSOCP, &busocp_reg_code);
if (ret)
return ret;
return (busocp_reg_code * BQ25980_BUSOCP_STEP_uA) + BQ25980_BUSOCP_OFFSET_uA;
}
static int bq25980_set_hiz(struct bq25980_device *bq, int setting)
{
return regmap_update_bits(bq->regmap, BQ25980_CHRGR_CTRL_2,
BQ25980_EN_HIZ, setting);
}
static int bq25980_set_input_curr_lim(struct bq25980_device *bq, int busocp)
{
unsigned int busocp_reg_code;
int ret;
if (!busocp)
return bq25980_set_hiz(bq, BQ25980_ENABLE_HIZ);
bq25980_set_hiz(bq, BQ25980_DISABLE_HIZ);
if (busocp < BQ25980_BUSOCP_MIN_uA)
busocp = BQ25980_BUSOCP_MIN_uA;
if (bq->state.bypass)
busocp = min(busocp, bq->chip_info->busocp_sc_max);
else
busocp = min(busocp, bq->chip_info->busocp_byp_max);
busocp_reg_code = (busocp - BQ25980_BUSOCP_OFFSET_uA)
/ BQ25980_BUSOCP_STEP_uA;
ret = regmap_write(bq->regmap, BQ25980_BUSOCP, busocp_reg_code);
if (ret)
return ret;
return regmap_write(bq->regmap, BQ25980_BUSOCP_ALM, busocp_reg_code);
}
static int bq25980_get_input_volt_lim(struct bq25980_device *bq)
{
unsigned int busovp_reg_code;
unsigned int busovp_offset;
unsigned int busovp_step;
int ret;
if (bq->state.bypass) {
busovp_step = bq->chip_info->busovp_byp_step;
busovp_offset = bq->chip_info->busovp_byp_offset;
} else {
busovp_step = bq->chip_info->busovp_sc_step;
busovp_offset = bq->chip_info->busovp_sc_offset;
}
ret = regmap_read(bq->regmap, BQ25980_BUSOVP, &busovp_reg_code);
if (ret)
return ret;
return (busovp_reg_code * busovp_step) + busovp_offset;
}
static int bq25980_set_input_volt_lim(struct bq25980_device *bq, int busovp)
{
unsigned int busovp_reg_code;
unsigned int busovp_step;
unsigned int busovp_offset;
int ret;
if (bq->state.bypass) {
busovp_step = bq->chip_info->busovp_byp_step;
busovp_offset = bq->chip_info->busovp_byp_offset;
if (busovp > bq->chip_info->busovp_byp_max)
busovp = bq->chip_info->busovp_byp_max;
else if (busovp < bq->chip_info->busovp_byp_min)
busovp = bq->chip_info->busovp_byp_min;
} else {
busovp_step = bq->chip_info->busovp_sc_step;
busovp_offset = bq->chip_info->busovp_sc_offset;
if (busovp > bq->chip_info->busovp_sc_max)
busovp = bq->chip_info->busovp_sc_max;
else if (busovp < bq->chip_info->busovp_sc_min)
busovp = bq->chip_info->busovp_sc_min;
}
busovp_reg_code = (busovp - busovp_offset) / busovp_step;
ret = regmap_write(bq->regmap, BQ25980_BUSOVP, busovp_reg_code);
if (ret)
return ret;
return regmap_write(bq->regmap, BQ25980_BUSOVP_ALM, busovp_reg_code);
}
static int bq25980_get_const_charge_curr(struct bq25980_device *bq)
{
unsigned int batocp_reg_code;
int ret;
ret = regmap_read(bq->regmap, BQ25980_BATOCP, &batocp_reg_code);
if (ret)
return ret;
return (batocp_reg_code & BQ25980_BATOCP_MASK) *
BQ25980_BATOCP_STEP_uA;
}
static int bq25980_set_const_charge_curr(struct bq25980_device *bq, int batocp)
{
unsigned int batocp_reg_code;
int ret;
batocp = max(batocp, BQ25980_BATOCP_MIN_uA);
batocp = min(batocp, bq->chip_info->batocp_max);
batocp_reg_code = batocp / BQ25980_BATOCP_STEP_uA;
ret = regmap_update_bits(bq->regmap, BQ25980_BATOCP,
BQ25980_BATOCP_MASK, batocp_reg_code);
if (ret)
return ret;
return regmap_update_bits(bq->regmap, BQ25980_BATOCP_ALM,
BQ25980_BATOCP_MASK, batocp_reg_code);
}
static int bq25980_get_const_charge_volt(struct bq25980_device *bq)
{
unsigned int batovp_reg_code;
int ret;
ret = regmap_read(bq->regmap, BQ25980_BATOVP, &batovp_reg_code);
if (ret)
return ret;
return ((batovp_reg_code * bq->chip_info->batovp_step) +
bq->chip_info->batovp_offset);
}
static int bq25980_set_const_charge_volt(struct bq25980_device *bq, int batovp)
{
unsigned int batovp_reg_code;
int ret;
if (batovp < bq->chip_info->batovp_min)
batovp = bq->chip_info->batovp_min;
if (batovp > bq->chip_info->batovp_max)
batovp = bq->chip_info->batovp_max;
batovp_reg_code = (batovp - bq->chip_info->batovp_offset) /
bq->chip_info->batovp_step;
ret = regmap_write(bq->regmap, BQ25980_BATOVP, batovp_reg_code);
if (ret)
return ret;
return regmap_write(bq->regmap, BQ25980_BATOVP_ALM, batovp_reg_code);
}
static int bq25980_set_bypass(struct bq25980_device *bq, bool en_bypass)
{
int ret;
if (en_bypass)
ret = regmap_update_bits(bq->regmap, BQ25980_CHRGR_CTRL_2,
BQ25980_EN_BYPASS, BQ25980_EN_BYPASS);
else
ret = regmap_update_bits(bq->regmap, BQ25980_CHRGR_CTRL_2,
BQ25980_EN_BYPASS, en_bypass);
if (ret)
return ret;
bq->state.bypass = en_bypass;
return bq->state.bypass;
}
static int bq25980_set_chg_en(struct bq25980_device *bq, bool en_chg)
{
int ret;
if (en_chg)
ret = regmap_update_bits(bq->regmap, BQ25980_CHRGR_CTRL_2,
BQ25980_CHG_EN, BQ25980_CHG_EN);
else
ret = regmap_update_bits(bq->regmap, BQ25980_CHRGR_CTRL_2,
BQ25980_CHG_EN, en_chg);
if (ret)
return ret;
bq->state.ce = en_chg;
return 0;
}
static int bq25980_get_adc_ibus(struct bq25980_device *bq)
{
int ibus_adc_lsb, ibus_adc_msb;
u16 ibus_adc;
int ret;
ret = regmap_read(bq->regmap, BQ25980_IBUS_ADC_MSB, &ibus_adc_msb);
if (ret)
return ret;
ret = regmap_read(bq->regmap, BQ25980_IBUS_ADC_LSB, &ibus_adc_lsb);
if (ret)
return ret;
ibus_adc = (ibus_adc_msb << 8) | ibus_adc_lsb;
if (ibus_adc_msb & BQ25980_ADC_POLARITY_BIT)
return ((ibus_adc ^ 0xffff) + 1) * BQ25980_ADC_CURR_STEP_uA;
return ibus_adc * BQ25980_ADC_CURR_STEP_uA;
}
static int bq25980_get_adc_vbus(struct bq25980_device *bq)
{
int vbus_adc_lsb, vbus_adc_msb;
u16 vbus_adc;
int ret;
ret = regmap_read(bq->regmap, BQ25980_VBUS_ADC_MSB, &vbus_adc_msb);
if (ret)
return ret;
ret = regmap_read(bq->regmap, BQ25980_VBUS_ADC_LSB, &vbus_adc_lsb);
if (ret)
return ret;
vbus_adc = (vbus_adc_msb << 8) | vbus_adc_lsb;
return vbus_adc * BQ25980_ADC_VOLT_STEP_uV;
}
static int bq25980_get_ibat_adc(struct bq25980_device *bq)
{
int ret;
int ibat_adc_lsb, ibat_adc_msb;
int ibat_adc;
ret = regmap_read(bq->regmap, BQ25980_IBAT_ADC_MSB, &ibat_adc_msb);
if (ret)
return ret;
ret = regmap_read(bq->regmap, BQ25980_IBAT_ADC_LSB, &ibat_adc_lsb);
if (ret)
return ret;
ibat_adc = (ibat_adc_msb << 8) | ibat_adc_lsb;
if (ibat_adc_msb & BQ25980_ADC_POLARITY_BIT)
return ((ibat_adc ^ 0xffff) + 1) * BQ25980_ADC_CURR_STEP_uA;
return ibat_adc * BQ25980_ADC_CURR_STEP_uA;
}
static int bq25980_get_adc_vbat(struct bq25980_device *bq)
{
int vsys_adc_lsb, vsys_adc_msb;
u16 vsys_adc;
int ret;
ret = regmap_read(bq->regmap, BQ25980_VBAT_ADC_MSB, &vsys_adc_msb);
if (ret)
return ret;
ret = regmap_read(bq->regmap, BQ25980_VBAT_ADC_LSB, &vsys_adc_lsb);
if (ret)
return ret;
vsys_adc = (vsys_adc_msb << 8) | vsys_adc_lsb;
return vsys_adc * BQ25980_ADC_VOLT_STEP_uV;
}
static int bq25980_get_state(struct bq25980_device *bq,
struct bq25980_state *state)
{
unsigned int chg_ctrl_2;
unsigned int stat1;
unsigned int stat2;
unsigned int stat3;
unsigned int stat4;
unsigned int ibat_adc_msb;
int ret;
ret = regmap_read(bq->regmap, BQ25980_STAT1, &stat1);
if (ret)
return ret;
ret = regmap_read(bq->regmap, BQ25980_STAT2, &stat2);
if (ret)
return ret;
ret = regmap_read(bq->regmap, BQ25980_STAT3, &stat3);
if (ret)
return ret;
ret = regmap_read(bq->regmap, BQ25980_STAT4, &stat4);
if (ret)
return ret;
ret = regmap_read(bq->regmap, BQ25980_CHRGR_CTRL_2, &chg_ctrl_2);
if (ret)
return ret;
ret = regmap_read(bq->regmap, BQ25980_IBAT_ADC_MSB, &ibat_adc_msb);
if (ret)
return ret;
state->dischg = ibat_adc_msb & BQ25980_ADC_POLARITY_BIT;
state->ovp = (stat1 & BQ25980_STAT1_OVP_MASK) |
(stat3 & BQ25980_STAT3_OVP_MASK);
state->ocp = (stat1 & BQ25980_STAT1_OCP_MASK) |
(stat2 & BQ25980_STAT2_OCP_MASK);
state->tflt = stat4 & BQ25980_STAT4_TFLT_MASK;
state->wdt = stat4 & BQ25980_WD_STAT;
state->online = stat3 & BQ25980_PRESENT_MASK;
state->ce = chg_ctrl_2 & BQ25980_CHG_EN;
state->hiz = chg_ctrl_2 & BQ25980_EN_HIZ;
state->bypass = chg_ctrl_2 & BQ25980_EN_BYPASS;
return 0;
}
static int bq25980_set_battery_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct bq25980_device *bq = power_supply_get_drvdata(psy);
int ret = 0;
switch (psp) {
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
ret = bq25980_set_const_charge_curr(bq, val->intval);
if (ret)
return ret;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
ret = bq25980_set_const_charge_volt(bq, val->intval);
if (ret)
return ret;
break;
default:
return -EINVAL;
}
return ret;
}
static int bq25980_get_battery_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct bq25980_device *bq = power_supply_get_drvdata(psy);
int ret = 0;
switch (psp) {
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
val->intval = bq->init_data.ichg_max;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
val->intval = bq->init_data.vreg_max;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
ret = bq25980_get_ibat_adc(bq);
val->intval = ret;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
ret = bq25980_get_adc_vbat(bq);
if (ret < 0)
return ret;
val->intval = ret;
break;
default:
return -EINVAL;
}
return ret;
}
static int bq25980_set_charger_property(struct power_supply *psy,
enum power_supply_property prop,
const union power_supply_propval *val)
{
struct bq25980_device *bq = power_supply_get_drvdata(psy);
int ret = -EINVAL;
switch (prop) {
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
ret = bq25980_set_input_curr_lim(bq, val->intval);
if (ret)
return ret;
break;
case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
ret = bq25980_set_input_volt_lim(bq, val->intval);
if (ret)
return ret;
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
ret = bq25980_set_bypass(bq, val->intval);
if (ret)
return ret;
break;
case POWER_SUPPLY_PROP_STATUS:
ret = bq25980_set_chg_en(bq, val->intval);
if (ret)
return ret;
break;
default:
return -EINVAL;
}
return ret;
}
static int bq25980_get_charger_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct bq25980_device *bq = power_supply_get_drvdata(psy);
struct bq25980_state state;
int ret = 0;
mutex_lock(&bq->lock);
ret = bq25980_get_state(bq, &state);
mutex_unlock(&bq->lock);
if (ret)
return ret;
switch (psp) {
case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = BQ25980_MANUFACTURER;
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = bq->model_name;
break;
case POWER_SUPPLY_PROP_ONLINE:
val->intval = state.online;
break;
case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
ret = bq25980_get_input_volt_lim(bq);
if (ret < 0)
return ret;
val->intval = ret;
break;
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
ret = bq25980_get_input_curr_lim(bq);
if (ret < 0)
return ret;
val->intval = ret;
break;
case POWER_SUPPLY_PROP_HEALTH:
val->intval = POWER_SUPPLY_HEALTH_GOOD;
if (state.tflt)
val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
else if (state.ovp)
val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
else if (state.ocp)
val->intval = POWER_SUPPLY_HEALTH_OVERCURRENT;
else if (state.wdt)
val->intval =
POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE;
break;
case POWER_SUPPLY_PROP_STATUS:
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
if ((state.ce) && (!state.hiz))
val->intval = POWER_SUPPLY_STATUS_CHARGING;
else if (state.dischg)
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
else if (!state.ce)
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
if (!state.ce)
val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
else if (state.bypass)
val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
else if (!state.bypass)
val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
ret = bq25980_get_adc_ibus(bq);
if (ret < 0)
return ret;
val->intval = ret;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
ret = bq25980_get_adc_vbus(bq);
if (ret < 0)
return ret;
val->intval = ret;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
ret = bq25980_get_const_charge_curr(bq);
if (ret < 0)
return ret;
val->intval = ret;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
ret = bq25980_get_const_charge_volt(bq);
if (ret < 0)
return ret;
val->intval = ret;
break;
default:
return -EINVAL;
}
return ret;
}
static bool bq25980_state_changed(struct bq25980_device *bq,
struct bq25980_state *new_state)
{
struct bq25980_state old_state;
mutex_lock(&bq->lock);
old_state = bq->state;
mutex_unlock(&bq->lock);
return (old_state.dischg != new_state->dischg ||
old_state.ovp != new_state->ovp ||
old_state.ocp != new_state->ocp ||
old_state.online != new_state->online ||
old_state.wdt != new_state->wdt ||
old_state.tflt != new_state->tflt ||
old_state.ce != new_state->ce ||
old_state.hiz != new_state->hiz ||
old_state.bypass != new_state->bypass);
}
static irqreturn_t bq25980_irq_handler_thread(int irq, void *private)
{
struct bq25980_device *bq = private;
struct bq25980_state state;
int ret;
ret = bq25980_get_state(bq, &state);
if (ret < 0)
goto irq_out;
if (!bq25980_state_changed(bq, &state))
goto irq_out;
mutex_lock(&bq->lock);
bq->state = state;
mutex_unlock(&bq->lock);
power_supply_changed(bq->charger);
irq_out:
return IRQ_HANDLED;
}
static enum power_supply_property bq25980_power_supply_props[] = {
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
};
static enum power_supply_property bq25980_battery_props[] = {
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
};
static char *bq25980_charger_supplied_to[] = {
"main-battery",
};
static int bq25980_property_is_writeable(struct power_supply *psy,
enum power_supply_property prop)
{
switch (prop) {
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
case POWER_SUPPLY_PROP_CHARGE_TYPE:
case POWER_SUPPLY_PROP_STATUS:
case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
return true;
default:
return false;
}
}
static const struct power_supply_desc bq25980_power_supply_desc = {
.name = "bq25980-charger",
.type = POWER_SUPPLY_TYPE_MAINS,
.properties = bq25980_power_supply_props,
.num_properties = ARRAY_SIZE(bq25980_power_supply_props),
.get_property = bq25980_get_charger_property,
.set_property = bq25980_set_charger_property,
.property_is_writeable = bq25980_property_is_writeable,
};
static struct power_supply_desc bq25980_battery_desc = {
.name = "bq25980-battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.get_property = bq25980_get_battery_property,
.set_property = bq25980_set_battery_property,
.properties = bq25980_battery_props,
.num_properties = ARRAY_SIZE(bq25980_battery_props),
.property_is_writeable = bq25980_property_is_writeable,
};
static bool bq25980_is_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case BQ25980_CHRGR_CTRL_2:
case BQ25980_STAT1...BQ25980_FLAG5:
case BQ25980_ADC_CONTROL1...BQ25980_TDIE_ADC_LSB:
return true;
default:
return false;
}
}
static const struct regmap_config bq25980_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = BQ25980_CHRGR_CTRL_6,
.reg_defaults = bq25980_reg_defs,
.num_reg_defaults = ARRAY_SIZE(bq25980_reg_defs),
.cache_type = REGCACHE_RBTREE,
.volatile_reg = bq25980_is_volatile_reg,
};
static const struct regmap_config bq25975_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = BQ25980_CHRGR_CTRL_6,
.reg_defaults = bq25975_reg_defs,
.num_reg_defaults = ARRAY_SIZE(bq25975_reg_defs),
.cache_type = REGCACHE_RBTREE,
.volatile_reg = bq25980_is_volatile_reg,
};
static const struct regmap_config bq25960_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = BQ25980_CHRGR_CTRL_6,
.reg_defaults = bq25960_reg_defs,
.num_reg_defaults = ARRAY_SIZE(bq25960_reg_defs),
.cache_type = REGCACHE_RBTREE,
.volatile_reg = bq25980_is_volatile_reg,
};
static const struct bq25980_chip_info bq25980_chip_info_tbl[] = {
[BQ25980] = {
.model_id = BQ25980,
.regmap_config = &bq25980_regmap_config,
.busocp_def = BQ25980_BUSOCP_DFLT_uA,
.busocp_sc_min = BQ25960_BUSOCP_SC_MAX_uA,
.busocp_sc_max = BQ25980_BUSOCP_SC_MAX_uA,
.busocp_byp_max = BQ25980_BUSOCP_BYP_MAX_uA,
.busocp_byp_min = BQ25980_BUSOCP_MIN_uA,
.busovp_sc_def = BQ25980_BUSOVP_DFLT_uV,
.busovp_byp_def = BQ25980_BUSOVP_BYPASS_DFLT_uV,
.busovp_sc_step = BQ25980_BUSOVP_SC_STEP_uV,
.busovp_sc_offset = BQ25980_BUSOVP_SC_OFFSET_uV,
.busovp_byp_step = BQ25980_BUSOVP_BYP_STEP_uV,
.busovp_byp_offset = BQ25980_BUSOVP_BYP_OFFSET_uV,
.busovp_sc_min = BQ25980_BUSOVP_SC_MIN_uV,
.busovp_sc_max = BQ25980_BUSOVP_SC_MAX_uV,
.busovp_byp_min = BQ25980_BUSOVP_BYP_MIN_uV,
.busovp_byp_max = BQ25980_BUSOVP_BYP_MAX_uV,
.batovp_def = BQ25980_BATOVP_DFLT_uV,
.batovp_max = BQ25980_BATOVP_MAX_uV,
.batovp_min = BQ25980_BATOVP_MIN_uV,
.batovp_step = BQ25980_BATOVP_STEP_uV,
.batovp_offset = BQ25980_BATOVP_OFFSET_uV,
.batocp_def = BQ25980_BATOCP_DFLT_uA,
.batocp_max = BQ25980_BATOCP_MAX_uA,
},
[BQ25975] = {
.model_id = BQ25975,
.regmap_config = &bq25975_regmap_config,
.busocp_def = BQ25975_BUSOCP_DFLT_uA,
.busocp_sc_min = BQ25975_BUSOCP_SC_MAX_uA,
.busocp_sc_max = BQ25975_BUSOCP_SC_MAX_uA,
.busocp_byp_min = BQ25980_BUSOCP_MIN_uA,
.busocp_byp_max = BQ25975_BUSOCP_BYP_MAX_uA,
.busovp_sc_def = BQ25975_BUSOVP_DFLT_uV,
.busovp_byp_def = BQ25975_BUSOVP_BYPASS_DFLT_uV,
.busovp_sc_step = BQ25975_BUSOVP_SC_STEP_uV,
.busovp_sc_offset = BQ25975_BUSOVP_SC_OFFSET_uV,
.busovp_byp_step = BQ25975_BUSOVP_BYP_STEP_uV,
.busovp_byp_offset = BQ25975_BUSOVP_BYP_OFFSET_uV,
.busovp_sc_min = BQ25975_BUSOVP_SC_MIN_uV,
.busovp_sc_max = BQ25975_BUSOVP_SC_MAX_uV,
.busovp_byp_min = BQ25975_BUSOVP_BYP_MIN_uV,
.busovp_byp_max = BQ25975_BUSOVP_BYP_MAX_uV,
.batovp_def = BQ25975_BATOVP_DFLT_uV,
.batovp_max = BQ25975_BATOVP_MAX_uV,
.batovp_min = BQ25975_BATOVP_MIN_uV,
.batovp_step = BQ25975_BATOVP_STEP_uV,
.batovp_offset = BQ25975_BATOVP_OFFSET_uV,
.batocp_def = BQ25980_BATOCP_DFLT_uA,
.batocp_max = BQ25980_BATOCP_MAX_uA,
},
[BQ25960] = {
.model_id = BQ25960,
.regmap_config = &bq25960_regmap_config,
.busocp_def = BQ25960_BUSOCP_DFLT_uA,
.busocp_sc_min = BQ25960_BUSOCP_SC_MAX_uA,
.busocp_sc_max = BQ25960_BUSOCP_SC_MAX_uA,
.busocp_byp_min = BQ25960_BUSOCP_SC_MAX_uA,
.busocp_byp_max = BQ25960_BUSOCP_BYP_MAX_uA,
.busovp_sc_def = BQ25975_BUSOVP_DFLT_uV,
.busovp_byp_def = BQ25975_BUSOVP_BYPASS_DFLT_uV,
.busovp_sc_step = BQ25960_BUSOVP_SC_STEP_uV,
.busovp_sc_offset = BQ25960_BUSOVP_SC_OFFSET_uV,
.busovp_byp_step = BQ25960_BUSOVP_BYP_STEP_uV,
.busovp_byp_offset = BQ25960_BUSOVP_BYP_OFFSET_uV,
.busovp_sc_min = BQ25960_BUSOVP_SC_MIN_uV,
.busovp_sc_max = BQ25960_BUSOVP_SC_MAX_uV,
.busovp_byp_min = BQ25960_BUSOVP_BYP_MIN_uV,
.busovp_byp_max = BQ25960_BUSOVP_BYP_MAX_uV,
.batovp_def = BQ25960_BATOVP_DFLT_uV,
.batovp_max = BQ25960_BATOVP_MAX_uV,
.batovp_min = BQ25960_BATOVP_MIN_uV,
.batovp_step = BQ25960_BATOVP_STEP_uV,
.batovp_offset = BQ25960_BATOVP_OFFSET_uV,
.batocp_def = BQ25960_BATOCP_DFLT_uA,
.batocp_max = BQ25960_BATOCP_MAX_uA,
},
};
static int bq25980_power_supply_init(struct bq25980_device *bq,
struct device *dev)
{
struct power_supply_config psy_cfg = { .drv_data = bq,
.of_node = dev->of_node, };
psy_cfg.supplied_to = bq25980_charger_supplied_to;
psy_cfg.num_supplicants = ARRAY_SIZE(bq25980_charger_supplied_to);
bq->charger = devm_power_supply_register(bq->dev,
&bq25980_power_supply_desc,
&psy_cfg);
if (IS_ERR(bq->charger))
return -EINVAL;
bq->battery = devm_power_supply_register(bq->dev,
&bq25980_battery_desc,
&psy_cfg);
if (IS_ERR(bq->battery))
return -EINVAL;
return 0;
}
static int bq25980_hw_init(struct bq25980_device *bq)
{
struct power_supply_battery_info bat_info = { };
int wd_reg_val = BQ25980_WATCHDOG_DIS;
int wd_max_val = BQ25980_NUM_WD_VAL - 1;
int ret = 0;
int curr_val;
int volt_val;
int i;
if (bq->watchdog_timer) {
if (bq->watchdog_timer >= bq25980_watchdog_time[wd_max_val])
wd_reg_val = wd_max_val;
else {
for (i = 0; i < wd_max_val; i++) {
if (bq->watchdog_timer > bq25980_watchdog_time[i] &&
bq->watchdog_timer < bq25980_watchdog_time[i + 1]) {
wd_reg_val = i;
break;
}
}
}
}
ret = regmap_update_bits(bq->regmap, BQ25980_CHRGR_CTRL_3,
BQ25980_WATCHDOG_MASK, wd_reg_val);
if (ret)
return ret;
ret = power_supply_get_battery_info(bq->charger, &bat_info);
if (ret) {
dev_warn(bq->dev, "battery info missing\n");
return -EINVAL;
}
bq->init_data.ichg_max = bat_info.constant_charge_current_max_ua;
bq->init_data.vreg_max = bat_info.constant_charge_voltage_max_uv;
if (bq->state.bypass) {
ret = regmap_update_bits(bq->regmap, BQ25980_CHRGR_CTRL_2,
BQ25980_EN_BYPASS, BQ25980_EN_BYPASS);
if (ret)
return ret;
curr_val = bq->init_data.bypass_ilim;
volt_val = bq->init_data.bypass_vlim;
} else {
curr_val = bq->init_data.sc_ilim;
volt_val = bq->init_data.sc_vlim;
}
ret = bq25980_set_input_curr_lim(bq, curr_val);
if (ret)
return ret;
ret = bq25980_set_input_volt_lim(bq, volt_val);
if (ret)
return ret;
return regmap_update_bits(bq->regmap, BQ25980_ADC_CONTROL1,
BQ25980_ADC_EN, BQ25980_ADC_EN);
}
static int bq25980_parse_dt(struct bq25980_device *bq)
{
int ret;
ret = device_property_read_u32(bq->dev, "ti,watchdog-timeout-ms",
&bq->watchdog_timer);
if (ret)
bq->watchdog_timer = BQ25980_WATCHDOG_MIN;
if (bq->watchdog_timer > BQ25980_WATCHDOG_MAX ||
bq->watchdog_timer < BQ25980_WATCHDOG_MIN)
return -EINVAL;
ret = device_property_read_u32(bq->dev,
"ti,sc-ovp-limit-microvolt",
&bq->init_data.sc_vlim);
if (ret)
bq->init_data.sc_vlim = bq->chip_info->busovp_sc_def;
if (bq->init_data.sc_vlim > bq->chip_info->busovp_sc_max ||
bq->init_data.sc_vlim < bq->chip_info->busovp_sc_min) {
dev_err(bq->dev, "SC ovp limit is out of range\n");
return -EINVAL;
}
ret = device_property_read_u32(bq->dev,
"ti,sc-ocp-limit-microamp",
&bq->init_data.sc_ilim);
if (ret)
bq->init_data.sc_ilim = bq->chip_info->busocp_def;
if (bq->init_data.sc_ilim > bq->chip_info->busocp_sc_max ||
bq->init_data.sc_ilim < bq->chip_info->busocp_sc_min) {
dev_err(bq->dev, "SC ocp limit is out of range\n");
return -EINVAL;
}
ret = device_property_read_u32(bq->dev,
"ti,bypass-ovp-limit-microvolt",
&bq->init_data.bypass_vlim);
if (ret)
bq->init_data.bypass_vlim = bq->chip_info->busovp_byp_def;
if (bq->init_data.bypass_vlim > bq->chip_info->busovp_byp_max ||
bq->init_data.bypass_vlim < bq->chip_info->busovp_byp_min) {
dev_err(bq->dev, "Bypass ovp limit is out of range\n");
return -EINVAL;
}
ret = device_property_read_u32(bq->dev,
"ti,bypass-ocp-limit-microamp",
&bq->init_data.bypass_ilim);
if (ret)
bq->init_data.bypass_ilim = bq->chip_info->busocp_def;
if (bq->init_data.bypass_ilim > bq->chip_info->busocp_byp_max ||
bq->init_data.bypass_ilim < bq->chip_info->busocp_byp_min) {
dev_err(bq->dev, "Bypass ocp limit is out of range\n");
return -EINVAL;
}
bq->state.bypass = device_property_read_bool(bq->dev,
"ti,bypass-enable");
return 0;
}
static int bq25980_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
struct bq25980_device *bq;
int ret;
bq = devm_kzalloc(dev, sizeof(*bq), GFP_KERNEL);
if (!bq)
return -ENOMEM;
bq->client = client;
bq->dev = dev;
mutex_init(&bq->lock);
strncpy(bq->model_name, id->name, I2C_NAME_SIZE);
bq->chip_info = &bq25980_chip_info_tbl[id->driver_data];
bq->regmap = devm_regmap_init_i2c(client,
bq->chip_info->regmap_config);
if (IS_ERR(bq->regmap)) {
dev_err(dev, "Failed to allocate register map\n");
return PTR_ERR(bq->regmap);
}
i2c_set_clientdata(client, bq);
ret = bq25980_parse_dt(bq);
if (ret) {
dev_err(dev, "Failed to read device tree properties%d\n", ret);
return ret;
}
if (client->irq) {
ret = devm_request_threaded_irq(dev, client->irq, NULL,
bq25980_irq_handler_thread,
IRQF_TRIGGER_FALLING |
IRQF_ONESHOT,
dev_name(&client->dev), bq);
if (ret)
return ret;
}
ret = bq25980_power_supply_init(bq, dev);
if (ret) {
dev_err(dev, "Failed to register power supply\n");
return ret;
}
ret = bq25980_hw_init(bq);
if (ret) {
dev_err(dev, "Cannot initialize the chip.\n");
return ret;
}
return 0;
}
static const struct i2c_device_id bq25980_i2c_ids[] = {
{ "bq25980", BQ25980 },
{ "bq25975", BQ25975 },
{ "bq25975", BQ25975 },
{},
};
MODULE_DEVICE_TABLE(i2c, bq25980_i2c_ids);
static const struct of_device_id bq25980_of_match[] = {
{ .compatible = "ti,bq25980", .data = (void *)BQ25980 },
{ .compatible = "ti,bq25975", .data = (void *)BQ25975 },
{ .compatible = "ti,bq25960", .data = (void *)BQ25960 },
{ },
};
MODULE_DEVICE_TABLE(of, bq25980_of_match);
static struct i2c_driver bq25980_driver = {
.driver = {
.name = "bq25980-charger",
.of_match_table = bq25980_of_match,
},
.probe = bq25980_probe,
.id_table = bq25980_i2c_ids,
};
module_i2c_driver(bq25980_driver);
MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
MODULE_AUTHOR("Ricardo Rivera-Matos <r-rivera-matos@ti.com>");
MODULE_DESCRIPTION("bq25980 charger driver");
MODULE_LICENSE("GPL v2");
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com/ */
#ifndef BQ25980_CHARGER_H
#define BQ25980_CHARGER_H
#define BQ25980_MANUFACTURER "Texas Instruments"
#define BQ25980_BATOVP 0x0
#define BQ25980_BATOVP_ALM 0x1
#define BQ25980_BATOCP 0x2
#define BQ25980_BATOCP_ALM 0x3
#define BQ25980_BATUCP_ALM 0x4
#define BQ25980_CHRGR_CTRL_1 0x5
#define BQ25980_BUSOVP 0x6
#define BQ25980_BUSOVP_ALM 0x7
#define BQ25980_BUSOCP 0x8
#define BQ25980_BUSOCP_ALM 0x9
#define BQ25980_TEMP_CONTROL 0xA
#define BQ25980_TDIE_ALM 0xB
#define BQ25980_TSBUS_FLT 0xC
#define BQ25980_TSBAT_FLG 0xD
#define BQ25980_VAC_CONTROL 0xE
#define BQ25980_CHRGR_CTRL_2 0xF
#define BQ25980_CHRGR_CTRL_3 0x10
#define BQ25980_CHRGR_CTRL_4 0x11
#define BQ25980_CHRGR_CTRL_5 0x12
#define BQ25980_STAT1 0x13
#define BQ25980_STAT2 0x14
#define BQ25980_STAT3 0x15
#define BQ25980_STAT4 0x16
#define BQ25980_STAT5 0x17
#define BQ25980_FLAG1 0x18
#define BQ25980_FLAG2 0x19
#define BQ25980_FLAG3 0x1A
#define BQ25980_FLAG4 0x1B
#define BQ25980_FLAG5 0x1C
#define BQ25980_MASK1 0x1D
#define BQ25980_MASK2 0x1E
#define BQ25980_MASK3 0x1F
#define BQ25980_MASK4 0x20
#define BQ25980_MASK5 0x21
#define BQ25980_DEVICE_INFO 0x22
#define BQ25980_ADC_CONTROL1 0x23
#define BQ25980_ADC_CONTROL2 0x24
#define BQ25980_IBUS_ADC_MSB 0x25
#define BQ25980_IBUS_ADC_LSB 0x26
#define BQ25980_VBUS_ADC_MSB 0x27
#define BQ25980_VBUS_ADC_LSB 0x28
#define BQ25980_VAC1_ADC_MSB 0x29
#define BQ25980_VAC1_ADC_LSB 0x2A
#define BQ25980_VAC2_ADC_MSB 0x2B
#define BQ25980_VAC2_ADC_LSB 0x2C
#define BQ25980_VOUT_ADC_MSB 0x2D
#define BQ25980_VOUT_ADC_LSB 0x2E
#define BQ25980_VBAT_ADC_MSB 0x2F
#define BQ25980_VBAT_ADC_LSB 0x30
#define BQ25980_IBAT_ADC_MSB 0x31
#define BQ25980_IBAT_ADC_LSB 0x32
#define BQ25980_TSBUS_ADC_MSB 0x33
#define BQ25980_TSBUS_ADC_LSB 0x34
#define BQ25980_TSBAT_ADC_MSB 0x35
#define BQ25980_TSBAT_ADC_LSB 0x36
#define BQ25980_TDIE_ADC_MSB 0x37
#define BQ25980_TDIE_ADC_LSB 0x38
#define BQ25980_DEGLITCH_TIME 0x39
#define BQ25980_CHRGR_CTRL_6 0x3A
#define BQ25980_BUSOCP_STEP_uA 250000
#define BQ25980_BUSOCP_OFFSET_uA 1000000
#define BQ25980_BUSOCP_DFLT_uA 4250000
#define BQ25975_BUSOCP_DFLT_uA 4250000
#define BQ25960_BUSOCP_DFLT_uA 3250000
#define BQ25980_BUSOCP_MIN_uA 1000000
#define BQ25980_BUSOCP_SC_MAX_uA 5750000
#define BQ25975_BUSOCP_SC_MAX_uA 5750000
#define BQ25960_BUSOCP_SC_MAX_uA 3750000
#define BQ25980_BUSOCP_BYP_MAX_uA 8500000
#define BQ25975_BUSOCP_BYP_MAX_uA 8500000
#define BQ25960_BUSOCP_BYP_MAX_uA 5750000
#define BQ25980_BUSOVP_SC_STEP_uV 100000
#define BQ25975_BUSOVP_SC_STEP_uV 50000
#define BQ25960_BUSOVP_SC_STEP_uV 50000
#define BQ25980_BUSOVP_SC_OFFSET_uV 14000000
#define BQ25975_BUSOVP_SC_OFFSET_uV 7000000
#define BQ25960_BUSOVP_SC_OFFSET_uV 7000000
#define BQ25980_BUSOVP_BYP_STEP_uV 50000
#define BQ25975_BUSOVP_BYP_STEP_uV 25000
#define BQ25960_BUSOVP_BYP_STEP_uV 25000
#define BQ25980_BUSOVP_BYP_OFFSET_uV 7000000
#define BQ25975_BUSOVP_BYP_OFFSET_uV 3500000
#define BQ25960_BUSOVP_BYP_OFFSET_uV 3500000
#define BQ25980_BUSOVP_DFLT_uV 17800000
#define BQ25980_BUSOVP_BYPASS_DFLT_uV 8900000
#define BQ25975_BUSOVP_DFLT_uV 8900000
#define BQ25975_BUSOVP_BYPASS_DFLT_uV 4450000
#define BQ25960_BUSOVP_DFLT_uV 8900000
#define BQ25980_BUSOVP_SC_MIN_uV 14000000
#define BQ25975_BUSOVP_SC_MIN_uV 7000000
#define BQ25960_BUSOVP_SC_MIN_uV 7000000
#define BQ25980_BUSOVP_BYP_MIN_uV 7000000
#define BQ25975_BUSOVP_BYP_MIN_uV 3500000
#define BQ25960_BUSOVP_BYP_MIN_uV 3500000
#define BQ25980_BUSOVP_SC_MAX_uV 22000000
#define BQ25975_BUSOVP_SC_MAX_uV 12750000
#define BQ25960_BUSOVP_SC_MAX_uV 12750000
#define BQ25980_BUSOVP_BYP_MAX_uV 12750000
#define BQ25975_BUSOVP_BYP_MAX_uV 6500000
#define BQ25960_BUSOVP_BYP_MAX_uV 6500000
#define BQ25980_BATOVP_STEP_uV 20000
#define BQ25975_BATOVP_STEP_uV 10000
#define BQ25960_BATOVP_STEP_uV 10000
#define BQ25980_BATOVP_OFFSET_uV 7000000
#define BQ25975_BATOVP_OFFSET_uV 3500000
#define BQ25960_BATOVP_OFFSET_uV 3500000
#define BQ25980_BATOVP_DFLT_uV 14000000
#define BQ25975_BATOVP_DFLT_uV 8900000
#define BQ25960_BATOVP_DFLT_uV 8900000
#define BQ25980_BATOVP_MIN_uV 7000000
#define BQ25975_BATOVP_MIN_uV 3500000
#define BQ25960_BATOVP_MIN_uV 3500000
#define BQ25980_BATOVP_MAX_uV 9540000
#define BQ25975_BATOVP_MAX_uV 4770000
#define BQ25960_BATOVP_MAX_uV 4770000
#define BQ25980_BATOCP_STEP_uA 100000
#define BQ25980_BATOCP_MASK GENMASK(6, 0)
#define BQ25980_BATOCP_DFLT_uA 8100000
#define BQ25960_BATOCP_DFLT_uA 6100000
#define BQ25980_BATOCP_MIN_uA 2000000
#define BQ25980_BATOCP_MAX_uA 11000000
#define BQ25975_BATOCP_MAX_uA 11000000
#define BQ25960_BATOCP_MAX_uA 7000000
#define BQ25980_ENABLE_HIZ 0xff
#define BQ25980_DISABLE_HIZ 0x0
#define BQ25980_EN_BYPASS BIT(3)
#define BQ25980_STAT1_OVP_MASK (BIT(6) | BIT(5) | BIT(0))
#define BQ25980_STAT3_OVP_MASK (BIT(7) | BIT(6))
#define BQ25980_STAT1_OCP_MASK BIT(3)
#define BQ25980_STAT2_OCP_MASK (BIT(6) | BIT(1))
#define BQ25980_STAT4_TFLT_MASK GENMASK(5, 1)
#define BQ25980_WD_STAT BIT(0)
#define BQ25980_PRESENT_MASK GENMASK(4, 2)
#define BQ25980_CHG_EN BIT(4)
#define BQ25980_EN_HIZ BIT(6)
#define BQ25980_ADC_EN BIT(7)
#define BQ25980_ADC_VOLT_STEP_uV 1000
#define BQ25980_ADC_CURR_STEP_uA 1000
#define BQ25980_ADC_POLARITY_BIT BIT(7)
#define BQ25980_WATCHDOG_MASK GENMASK(4, 3)
#define BQ25980_WATCHDOG_DIS BIT(2)
#define BQ25980_WATCHDOG_MAX 300000
#define BQ25980_WATCHDOG_MIN 0
#define BQ25980_NUM_WD_VAL 4
#endif /* BQ25980_CHARGER_H */
// SPDX-License-Identifier: GPL-2.0
/*
* BQ27xxx battery driver
*
......@@ -9,14 +10,6 @@
*
* Based on a previous work by Copyright (C) 2008 Texas Instruments, Inc.
*
* This package is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* Datasheets:
* https://www.ti.com/product/bq27000
* https://www.ti.com/product/bq27200
......@@ -45,6 +38,7 @@
* https://www.ti.com/product/bq27621-g1
* https://www.ti.com/product/bq27z561
* https://www.ti.com/product/bq28z610
* https://www.ti.com/product/bq34z100-g1
*/
#include <linux/device.h>
......@@ -483,6 +477,26 @@ static u8
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x22,
BQ27XXX_DM_REG_ROWS,
},
bq34z100_regs[BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_CTRL] = 0x00,
[BQ27XXX_REG_TEMP] = 0x0c,
[BQ27XXX_REG_INT_TEMP] = 0x2a,
[BQ27XXX_REG_VOLT] = 0x08,
[BQ27XXX_REG_AI] = 0x0a,
[BQ27XXX_REG_FLAGS] = 0x0e,
[BQ27XXX_REG_TTE] = 0x18,
[BQ27XXX_REG_TTF] = 0x1a,
[BQ27XXX_REG_TTES] = 0x1e,
[BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
[BQ27XXX_REG_NAC] = INVALID_REG_ADDR,
[BQ27XXX_REG_FCC] = 0x06,
[BQ27XXX_REG_CYCT] = 0x2c,
[BQ27XXX_REG_AE] = 0x24,
[BQ27XXX_REG_SOC] = 0x02,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x22,
BQ27XXX_DM_REG_ROWS,
};
static enum power_supply_property bq27000_props[] = {
......@@ -757,6 +771,27 @@ static enum power_supply_property bq28z610_props[] = {
POWER_SUPPLY_PROP_MANUFACTURER,
};
static enum power_supply_property bq34z100_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_ENERGY_NOW,
POWER_SUPPLY_PROP_POWER_AVG,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_MANUFACTURER,
};
struct bq27xxx_dm_reg {
u8 subclass_id;
u8 offset;
......@@ -854,13 +889,17 @@ static struct bq27xxx_dm_reg bq27621_dm_regs[] = {
#define bq27z561_dm_regs 0
#define bq28z610_dm_regs 0
#define BQ27XXX_O_ZERO 0x00000001
#define BQ27XXX_O_OTDC 0x00000002 /* has OTC/OTD overtemperature flags */
#define BQ27XXX_O_UTOT 0x00000004 /* has OT overtemperature flag */
#define BQ27XXX_O_CFGUP 0x00000008
#define BQ27XXX_O_RAM 0x00000010
#define BQ27Z561_O_BITS 0x00000020
#define bq34z100_dm_regs 0
#define BQ27XXX_O_ZERO BIT(0)
#define BQ27XXX_O_OTDC BIT(1) /* has OTC/OTD overtemperature flags */
#define BQ27XXX_O_UTOT BIT(2) /* has OT overtemperature flag */
#define BQ27XXX_O_CFGUP BIT(3)
#define BQ27XXX_O_RAM BIT(4)
#define BQ27Z561_O_BITS BIT(5)
#define BQ27XXX_O_SOC_SI BIT(6) /* SoC is single register */
#define BQ27XXX_O_HAS_CI BIT(7) /* has Capacity Inaccurate flag */
#define BQ27XXX_O_MUL_CHEM BIT(8) /* multiple chemistries supported */
#define BQ27XXX_DATA(ref, key, opt) { \
.opts = (opt), \
......@@ -878,8 +917,8 @@ static struct {
enum power_supply_property *props;
size_t props_size;
} bq27xxx_chip_data[] = {
[BQ27000] = BQ27XXX_DATA(bq27000, 0 , BQ27XXX_O_ZERO),
[BQ27010] = BQ27XXX_DATA(bq27010, 0 , BQ27XXX_O_ZERO),
[BQ27000] = BQ27XXX_DATA(bq27000, 0 , BQ27XXX_O_ZERO | BQ27XXX_O_SOC_SI | BQ27XXX_O_HAS_CI),
[BQ27010] = BQ27XXX_DATA(bq27010, 0 , BQ27XXX_O_ZERO | BQ27XXX_O_SOC_SI | BQ27XXX_O_HAS_CI),
[BQ2750X] = BQ27XXX_DATA(bq2750x, 0 , BQ27XXX_O_OTDC),
[BQ2751X] = BQ27XXX_DATA(bq2751x, 0 , BQ27XXX_O_OTDC),
[BQ2752X] = BQ27XXX_DATA(bq2752x, 0 , BQ27XXX_O_OTDC),
......@@ -907,6 +946,8 @@ static struct {
[BQ27621] = BQ27XXX_DATA(bq27621, 0x80008000, BQ27XXX_O_UTOT | BQ27XXX_O_CFGUP | BQ27XXX_O_RAM),
[BQ27Z561] = BQ27XXX_DATA(bq27z561, 0 , BQ27Z561_O_BITS),
[BQ28Z610] = BQ27XXX_DATA(bq28z610, 0 , BQ27Z561_O_BITS),
[BQ34Z100] = BQ27XXX_DATA(bq34z100, 0 , BQ27XXX_O_OTDC | BQ27XXX_O_SOC_SI | \
BQ27XXX_O_HAS_CI | BQ27XXX_O_MUL_CHEM),
};
static DEFINE_MUTEX(bq27xxx_list_lock);
......@@ -1426,7 +1467,7 @@ static int bq27xxx_battery_read_soc(struct bq27xxx_device_info *di)
{
int soc;
if (di->opts & BQ27XXX_O_ZERO)
if (di->opts & BQ27XXX_O_SOC_SI)
soc = bq27xxx_read(di, BQ27XXX_REG_SOC, true);
else
soc = bq27xxx_read(di, BQ27XXX_REG_SOC, false);
......@@ -1664,7 +1705,7 @@ static int bq27xxx_battery_read_health(struct bq27xxx_device_info *di)
void bq27xxx_battery_update(struct bq27xxx_device_info *di)
{
struct bq27xxx_reg_cache cache = {0, };
bool has_ci_flag = di->opts & BQ27XXX_O_ZERO;
bool has_ci_flag = di->opts & BQ27XXX_O_HAS_CI;
bool has_singe_flag = di->opts & BQ27XXX_O_ZERO;
cache.flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, has_singe_flag);
......@@ -1772,8 +1813,6 @@ static int bq27xxx_battery_status(struct bq27xxx_device_info *di,
status = POWER_SUPPLY_STATUS_FULL;
else if (di->cache.flags & BQ27000_FLAG_CHGS)
status = POWER_SUPPLY_STATUS_CHARGING;
else if (power_supply_am_i_supplied(di->bat) > 0)
status = POWER_SUPPLY_STATUS_NOT_CHARGING;
else
status = POWER_SUPPLY_STATUS_DISCHARGING;
} else if (di->opts & BQ27Z561_O_BITS) {
......@@ -1792,6 +1831,10 @@ static int bq27xxx_battery_status(struct bq27xxx_device_info *di,
status = POWER_SUPPLY_STATUS_CHARGING;
}
if ((status == POWER_SUPPLY_STATUS_DISCHARGING) &&
(power_supply_am_i_supplied(di->bat) > 0))
status = POWER_SUPPLY_STATUS_NOT_CHARGING;
val->intval = status;
return 0;
......@@ -1916,6 +1959,9 @@ static int bq27xxx_battery_get_property(struct power_supply *psy,
ret = bq27xxx_simple_value(di->cache.time_to_full, val);
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
if (di->opts & BQ27XXX_O_MUL_CHEM)
val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
else
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
break;
case POWER_SUPPLY_PROP_CHARGE_NOW:
......@@ -1992,13 +2038,9 @@ int bq27xxx_battery_setup(struct bq27xxx_device_info *di)
psy_desc->external_power_changed = bq27xxx_external_power_changed;
di->bat = power_supply_register_no_ws(di->dev, psy_desc, &psy_cfg);
if (IS_ERR(di->bat)) {
if (PTR_ERR(di->bat) == -EPROBE_DEFER)
dev_dbg(di->dev, "failed to register battery, deferring probe\n");
else
dev_err(di->dev, "failed to register battery\n");
return PTR_ERR(di->bat);
}
if (IS_ERR(di->bat))
return dev_err_probe(di->dev, PTR_ERR(di->bat),
"failed to register battery\n");
bq27xxx_battery_settings(di);
bq27xxx_battery_update(di);
......
// SPDX-License-Identifier: GPL-2.0
/*
* BQ27xxx battery monitor HDQ/1-wire driver
*
* Copyright (C) 2007-2017 Texas Instruments Incorporated - https://www.ti.com/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
* kind, whether express or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/kernel.h>
......
// SPDX-License-Identifier: GPL-2.0
/*
* BQ27xxx battery monitor I2C driver
*
* Copyright (C) 2015 Texas Instruments Incorporated - https://www.ti.com/
* Andrew F. Davis <afd@ti.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
* kind, whether express or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/i2c.h>
......@@ -255,6 +247,7 @@ static const struct i2c_device_id bq27xxx_i2c_id_table[] = {
{ "bq27621", BQ27621 },
{ "bq27z561", BQ27Z561 },
{ "bq28z610", BQ28Z610 },
{ "bq34z100", BQ34Z100 },
{},
};
MODULE_DEVICE_TABLE(i2c, bq27xxx_i2c_id_table);
......@@ -290,6 +283,7 @@ static const struct of_device_id bq27xxx_battery_i2c_of_match_table[] = {
{ .compatible = "ti,bq27621" },
{ .compatible = "ti,bq27z561" },
{ .compatible = "ti,bq28z610" },
{ .compatible = "ti,bq34z100" },
{},
};
MODULE_DEVICE_TABLE(of, bq27xxx_battery_i2c_of_match_table);
......
......@@ -26,6 +26,29 @@
#include <linux/of.h>
#include <linux/thermal.h>
static struct {
const char *name;
u64 extcon_type;
} extcon_mapping[] = {
/* Current textual representations */
{ "USB", EXTCON_USB },
{ "USB-HOST", EXTCON_USB_HOST },
{ "SDP", EXTCON_CHG_USB_SDP },
{ "DCP", EXTCON_CHG_USB_DCP },
{ "CDP", EXTCON_CHG_USB_CDP },
{ "ACA", EXTCON_CHG_USB_ACA },
{ "FAST-CHARGER", EXTCON_CHG_USB_FAST },
{ "SLOW-CHARGER", EXTCON_CHG_USB_SLOW },
{ "WPT", EXTCON_CHG_WPT },
{ "PD", EXTCON_CHG_USB_PD },
{ "DOCK", EXTCON_DOCK },
{ "JIG", EXTCON_JIG },
{ "MECHANICAL", EXTCON_MECHANICAL },
/* Deprecated textual representations */
{ "TA", EXTCON_CHG_USB_SDP },
{ "CHARGE-DOWNSTREAM", EXTCON_CHG_USB_CDP },
};
/*
* Default temperature threshold for charging.
* Every temperature units are in tenth of centigrade.
......@@ -33,18 +56,6 @@
#define CM_DEFAULT_RECHARGE_TEMP_DIFF 50
#define CM_DEFAULT_CHARGE_TEMP_MAX 500
static const char * const default_event_names[] = {
[CM_EVENT_UNKNOWN] = "Unknown",
[CM_EVENT_BATT_FULL] = "Battery Full",
[CM_EVENT_BATT_IN] = "Battery Inserted",
[CM_EVENT_BATT_OUT] = "Battery Pulled Out",
[CM_EVENT_BATT_OVERHEAT] = "Battery Overheat",
[CM_EVENT_BATT_COLD] = "Battery Cold",
[CM_EVENT_EXT_PWR_IN_OUT] = "External Power Attach/Detach",
[CM_EVENT_CHG_START_STOP] = "Charging Start/Stop",
[CM_EVENT_OTHERS] = "Other battery events"
};
/*
* Regard CM_JIFFIES_SMALL jiffies is small enough to ignore for
* delayed works so that we can run delayed works with CM_JIFFIES_SMALL
......@@ -61,8 +72,6 @@ static const char * const default_event_names[] = {
*/
#define CM_RTC_SMALL (2)
#define UEVENT_BUF_SIZE 32
static LIST_HEAD(cm_list);
static DEFINE_MUTEX(cm_list_mtx);
......@@ -285,6 +294,19 @@ static bool is_full_charged(struct charger_manager *cm)
if (!fuel_gauge)
return false;
/* Full, if it's over the fullbatt voltage */
if (desc->fullbatt_uV > 0) {
ret = get_batt_uV(cm, &uV);
if (!ret) {
/* Battery is already full, checks voltage drop. */
if (cm->battery_status == POWER_SUPPLY_STATUS_FULL
&& desc->fullbatt_vchkdrop_uV)
uV += desc->fullbatt_vchkdrop_uV;
if (uV >= desc->fullbatt_uV)
return true;
}
}
if (desc->fullbatt_full_capacity > 0) {
val.intval = 0;
......@@ -297,15 +319,6 @@ static bool is_full_charged(struct charger_manager *cm)
}
}
/* Full, if it's over the fullbatt voltage */
if (desc->fullbatt_uV > 0) {
ret = get_batt_uV(cm, &uV);
if (!ret && uV >= desc->fullbatt_uV) {
is_full = true;
goto out;
}
}
/* Full, if the capacity is more than fullbatt_soc */
if (desc->fullbatt_soc > 0) {
val.intval = 0;
......@@ -426,122 +439,6 @@ static int try_charger_enable(struct charger_manager *cm, bool enable)
return err;
}
/**
* try_charger_restart - Restart charging.
* @cm: the Charger Manager representing the battery.
*
* Restart charging by turning off and on the charger.
*/
static int try_charger_restart(struct charger_manager *cm)
{
int err;
if (cm->emergency_stop)
return -EAGAIN;
err = try_charger_enable(cm, false);
if (err)
return err;
return try_charger_enable(cm, true);
}
/**
* uevent_notify - Let users know something has changed.
* @cm: the Charger Manager representing the battery.
* @event: the event string.
*
* If @event is null, it implies that uevent_notify is called
* by resume function. When called in the resume function, cm_suspended
* should be already reset to false in order to let uevent_notify
* notify the recent event during the suspend to users. While
* suspended, uevent_notify does not notify users, but tracks
* events so that uevent_notify can notify users later after resumed.
*/
static void uevent_notify(struct charger_manager *cm, const char *event)
{
static char env_str[UEVENT_BUF_SIZE + 1] = "";
static char env_str_save[UEVENT_BUF_SIZE + 1] = "";
if (cm_suspended) {
/* Nothing in suspended-event buffer */
if (env_str_save[0] == 0) {
if (!strncmp(env_str, event, UEVENT_BUF_SIZE))
return; /* status not changed */
strncpy(env_str_save, event, UEVENT_BUF_SIZE);
return;
}
if (!strncmp(env_str_save, event, UEVENT_BUF_SIZE))
return; /* Duplicated. */
strncpy(env_str_save, event, UEVENT_BUF_SIZE);
return;
}
if (event == NULL) {
/* No messages pending */
if (!env_str_save[0])
return;
strncpy(env_str, env_str_save, UEVENT_BUF_SIZE);
kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE);
env_str_save[0] = 0;
return;
}
/* status not changed */
if (!strncmp(env_str, event, UEVENT_BUF_SIZE))
return;
/* save the status and notify the update */
strncpy(env_str, event, UEVENT_BUF_SIZE);
kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE);
dev_info(cm->dev, "%s\n", event);
}
/**
* fullbatt_vchk - Check voltage drop some times after "FULL" event.
* @work: the work_struct appointing the function
*
* If a user has designated "fullbatt_vchkdrop_ms/uV" values with
* charger_desc, Charger Manager checks voltage drop after the battery
* "FULL" event. It checks whether the voltage has dropped more than
* fullbatt_vchkdrop_uV by calling this function after fullbatt_vchkrop_ms.
*/
static void fullbatt_vchk(struct work_struct *work)
{
struct delayed_work *dwork = to_delayed_work(work);
struct charger_manager *cm = container_of(dwork,
struct charger_manager, fullbatt_vchk_work);
struct charger_desc *desc = cm->desc;
int batt_uV, err, diff;
/* remove the appointment for fullbatt_vchk */
cm->fullbatt_vchk_jiffies_at = 0;
if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms)
return;
err = get_batt_uV(cm, &batt_uV);
if (err) {
dev_err(cm->dev, "%s: get_batt_uV error(%d)\n", __func__, err);
return;
}
diff = desc->fullbatt_uV - batt_uV;
if (diff < 0)
return;
dev_info(cm->dev, "VBATT dropped %duV after full-batt\n", diff);
if (diff > desc->fullbatt_vchkdrop_uV) {
try_charger_restart(cm);
uevent_notify(cm, "Recharging");
}
}
/**
* check_charging_duration - Monitor charging/discharging duration
* @cm: the Charger Manager representing the battery.
......@@ -569,19 +466,14 @@ static int check_charging_duration(struct charger_manager *cm)
if (duration > desc->charging_max_duration_ms) {
dev_info(cm->dev, "Charging duration exceed %ums\n",
desc->charging_max_duration_ms);
uevent_notify(cm, "Discharging");
try_charger_enable(cm, false);
ret = true;
}
} else if (is_ext_pwr_online(cm) && !cm->charger_enabled) {
} else if (cm->battery_status == POWER_SUPPLY_STATUS_NOT_CHARGING) {
duration = curr - cm->charging_end_time;
if (duration > desc->discharging_max_duration_ms &&
is_ext_pwr_online(cm)) {
if (duration > desc->discharging_max_duration_ms) {
dev_info(cm->dev, "Discharging duration exceed %ums\n",
desc->discharging_max_duration_ms);
uevent_notify(cm, "Recharging");
try_charger_enable(cm, true);
ret = true;
}
}
......@@ -657,13 +549,52 @@ static int cm_check_thermal_status(struct charger_manager *cm)
}
if (temp > upper_limit)
ret = CM_EVENT_BATT_OVERHEAT;
ret = CM_BATT_OVERHEAT;
else if (temp < lower_limit)
ret = CM_EVENT_BATT_COLD;
ret = CM_BATT_COLD;
else
ret = CM_BATT_OK;
cm->emergency_stop = ret;
return ret;
}
/**
* cm_get_target_status - Check current status and get next target status.
* @cm: the Charger Manager representing the battery.
*/
static int cm_get_target_status(struct charger_manager *cm)
{
if (!is_ext_pwr_online(cm))
return POWER_SUPPLY_STATUS_DISCHARGING;
if (cm_check_thermal_status(cm)) {
/* Check if discharging duration exeeds limit. */
if (check_charging_duration(cm))
goto charging_ok;
return POWER_SUPPLY_STATUS_NOT_CHARGING;
}
switch (cm->battery_status) {
case POWER_SUPPLY_STATUS_CHARGING:
/* Check if charging duration exeeds limit. */
if (check_charging_duration(cm))
return POWER_SUPPLY_STATUS_FULL;
fallthrough;
case POWER_SUPPLY_STATUS_FULL:
if (is_full_charged(cm))
return POWER_SUPPLY_STATUS_FULL;
fallthrough;
default:
break;
}
charging_ok:
/* Charging is allowed. */
return POWER_SUPPLY_STATUS_CHARGING;
}
/**
* _cm_monitor - Monitor the temperature and return true for exceptions.
* @cm: the Charger Manager representing the battery.
......@@ -673,60 +604,18 @@ static int cm_check_thermal_status(struct charger_manager *cm)
*/
static bool _cm_monitor(struct charger_manager *cm)
{
int temp_alrt;
int target;
temp_alrt = cm_check_thermal_status(cm);
target = cm_get_target_status(cm);
/* It has been stopped already */
if (temp_alrt && cm->emergency_stop)
return false;
/*
* Check temperature whether overheat or cold.
* If temperature is out of range normal state, stop charging.
*/
if (temp_alrt) {
cm->emergency_stop = temp_alrt;
if (!try_charger_enable(cm, false))
uevent_notify(cm, default_event_names[temp_alrt]);
try_charger_enable(cm, (target == POWER_SUPPLY_STATUS_CHARGING));
/*
* Check whole charging duration and discharging duration
* after full-batt.
*/
} else if (!cm->emergency_stop && check_charging_duration(cm)) {
dev_dbg(cm->dev,
"Charging/Discharging duration is out of range\n");
/*
* Check dropped voltage of battery. If battery voltage is more
* dropped than fullbatt_vchkdrop_uV after fully charged state,
* charger-manager have to recharge battery.
*/
} else if (!cm->emergency_stop && is_ext_pwr_online(cm) &&
!cm->charger_enabled) {
fullbatt_vchk(&cm->fullbatt_vchk_work.work);
/*
* Check whether fully charged state to protect overcharge
* if charger-manager is charging for battery.
*/
} else if (!cm->emergency_stop && is_full_charged(cm) &&
cm->charger_enabled) {
dev_info(cm->dev, "EVENT_HANDLE: Battery Fully Charged\n");
uevent_notify(cm, default_event_names[CM_EVENT_BATT_FULL]);
try_charger_enable(cm, false);
fullbatt_vchk(&cm->fullbatt_vchk_work.work);
} else {
cm->emergency_stop = 0;
if (is_ext_pwr_online(cm)) {
if (!try_charger_enable(cm, true))
uevent_notify(cm, "CHARGING");
}
if (cm->battery_status != target) {
cm->battery_status = target;
power_supply_changed(cm->charger_psy);
}
return true;
return (cm->battery_status == POWER_SUPPLY_STATUS_NOT_CHARGING);
}
/**
......@@ -819,66 +708,6 @@ static void cm_monitor_poller(struct work_struct *work)
schedule_work(&setup_polling);
}
/**
* fullbatt_handler - Event handler for CM_EVENT_BATT_FULL
* @cm: the Charger Manager representing the battery.
*/
static void fullbatt_handler(struct charger_manager *cm)
{
struct charger_desc *desc = cm->desc;
if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms)
goto out;
if (cm_suspended)
device_set_wakeup_capable(cm->dev, true);
mod_delayed_work(cm_wq, &cm->fullbatt_vchk_work,
msecs_to_jiffies(desc->fullbatt_vchkdrop_ms));
cm->fullbatt_vchk_jiffies_at = jiffies + msecs_to_jiffies(
desc->fullbatt_vchkdrop_ms);
if (cm->fullbatt_vchk_jiffies_at == 0)
cm->fullbatt_vchk_jiffies_at = 1;
out:
dev_info(cm->dev, "EVENT_HANDLE: Battery Fully Charged\n");
uevent_notify(cm, default_event_names[CM_EVENT_BATT_FULL]);
}
/**
* battout_handler - Event handler for CM_EVENT_BATT_OUT
* @cm: the Charger Manager representing the battery.
*/
static void battout_handler(struct charger_manager *cm)
{
if (cm_suspended)
device_set_wakeup_capable(cm->dev, true);
if (!is_batt_present(cm)) {
dev_emerg(cm->dev, "Battery Pulled Out!\n");
uevent_notify(cm, default_event_names[CM_EVENT_BATT_OUT]);
} else {
uevent_notify(cm, "Battery Reinserted?");
}
}
/**
* misc_event_handler - Handler for other events
* @cm: the Charger Manager representing the battery.
* @type: the Charger Manager representing the battery.
*/
static void misc_event_handler(struct charger_manager *cm,
enum cm_event_types type)
{
if (cm_suspended)
device_set_wakeup_capable(cm->dev, true);
if (is_polling_required(cm) && cm->desc->polling_interval_ms)
schedule_work(&setup_polling);
uevent_notify(cm, default_event_names[type]);
}
static int charger_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
......@@ -891,12 +720,7 @@ static int charger_get_property(struct power_supply *psy,
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
if (is_charging(cm))
val->intval = POWER_SUPPLY_STATUS_CHARGING;
else if (is_ext_pwr_online(cm))
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
else
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
val->intval = cm->battery_status;
break;
case POWER_SUPPLY_PROP_HEALTH:
if (cm->emergency_stop > 0)
......@@ -925,7 +749,6 @@ static int charger_get_property(struct power_supply *psy,
POWER_SUPPLY_PROP_CURRENT_NOW, val);
break;
case POWER_SUPPLY_PROP_TEMP:
case POWER_SUPPLY_PROP_TEMP_AMBIENT:
return cm_get_battery_temperature(cm, &val->intval);
case POWER_SUPPLY_PROP_CAPACITY:
if (!is_batt_present(cm)) {
......@@ -981,35 +804,13 @@ static int charger_get_property(struct power_supply *psy,
val->intval = 0;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
if (is_full_charged(cm))
val->intval = 1;
else
val->intval = 0;
ret = 0;
break;
case POWER_SUPPLY_PROP_CHARGE_NOW:
if (is_charging(cm)) {
fuel_gauge = power_supply_get_by_name(
cm->desc->psy_fuel_gauge);
fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
if (!fuel_gauge) {
ret = -ENODEV;
break;
}
ret = power_supply_get_property(fuel_gauge,
POWER_SUPPLY_PROP_CHARGE_NOW,
val);
if (ret) {
val->intval = 1;
ret = 0;
} else {
/* If CHARGE_NOW is supplied, use it */
val->intval = (val->intval > 0) ?
val->intval : 1;
}
} else {
val->intval = 0;
}
ret = power_supply_get_property(fuel_gauge, psp, val);
break;
default:
return -EINVAL;
......@@ -1028,13 +829,12 @@ static enum power_supply_property default_charger_props[] = {
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_CHARGE_FULL,
/*
* Optional properties are:
* POWER_SUPPLY_PROP_CHARGE_FULL,
* POWER_SUPPLY_PROP_CHARGE_NOW,
* POWER_SUPPLY_PROP_CURRENT_NOW,
* POWER_SUPPLY_PROP_TEMP, and
* POWER_SUPPLY_PROP_TEMP_AMBIENT,
* POWER_SUPPLY_PROP_TEMP,
*/
};
......@@ -1069,21 +869,6 @@ static bool cm_setup_timer(void)
mutex_lock(&cm_list_mtx);
list_for_each_entry(cm, &cm_list, entry) {
unsigned int fbchk_ms = 0;
/* fullbatt_vchk is required. setup timer for that */
if (cm->fullbatt_vchk_jiffies_at) {
fbchk_ms = jiffies_to_msecs(cm->fullbatt_vchk_jiffies_at
- jiffies);
if (time_is_before_eq_jiffies(
cm->fullbatt_vchk_jiffies_at) ||
msecs_to_jiffies(fbchk_ms) < CM_JIFFIES_SMALL) {
fullbatt_vchk(&cm->fullbatt_vchk_work.work);
fbchk_ms = 0;
}
}
CM_MIN_VALID(wakeup_ms, fbchk_ms);
/* Skip if polling is not required for this CM */
if (!is_polling_required(cm) && !cm->emergency_stop)
continue;
......@@ -1145,7 +930,8 @@ static void charger_extcon_work(struct work_struct *work)
cable->min_uA, cable->max_uA);
}
try_charger_enable(cable->cm, cable->attached);
cancel_delayed_work(&cm_monitor_work);
queue_delayed_work(cm_wq, &cm_monitor_work, 0);
}
/**
......@@ -1168,15 +954,6 @@ static int charger_extcon_notifier(struct notifier_block *self,
*/
cable->attached = event;
/*
* Setup monitoring to check battery state
* when charger cable is attached.
*/
if (cable->attached && is_polling_required(cable->cm)) {
cancel_work_sync(&setup_polling);
schedule_work(&setup_polling);
}
/*
* Setup work for controlling charger(regulator)
* according to charger cable.
......@@ -1196,7 +973,8 @@ static int charger_extcon_notifier(struct notifier_block *self,
static int charger_extcon_init(struct charger_manager *cm,
struct charger_cable *cable)
{
int ret;
int ret, i;
u64 extcon_type = EXTCON_NONE;
/*
* Charger manager use Extcon framework to identify
......@@ -1205,14 +983,39 @@ static int charger_extcon_init(struct charger_manager *cm,
*/
INIT_WORK(&cable->wq, charger_extcon_work);
cable->nb.notifier_call = charger_extcon_notifier;
ret = extcon_register_interest(&cable->extcon_dev,
cable->extcon_name, cable->name, &cable->nb);
if (ret < 0) {
pr_info("Cannot register extcon_dev for %s(cable: %s)\n",
cable->extcon_dev = extcon_get_extcon_dev(cable->extcon_name);
if (IS_ERR_OR_NULL(cable->extcon_dev)) {
pr_err("Cannot find extcon_dev for %s (cable: %s)\n",
cable->extcon_name, cable->name);
if (cable->extcon_dev == NULL)
return -EPROBE_DEFER;
else
return PTR_ERR(cable->extcon_dev);
}
for (i = 0; i < ARRAY_SIZE(extcon_mapping); i++) {
if (!strcmp(cable->name, extcon_mapping[i].name)) {
extcon_type = extcon_mapping[i].extcon_type;
break;
}
}
if (extcon_type == EXTCON_NONE) {
pr_err("Cannot find cable for type %s", cable->name);
return -EINVAL;
}
cable->extcon_type = extcon_type;
ret = devm_extcon_register_notifier(cm->dev, cable->extcon_dev,
cable->extcon_type, &cable->nb);
if (ret < 0) {
pr_err("Cannot register extcon_dev for %s (cable: %s)\n",
cable->extcon_name, cable->name);
return ret;
}
return 0;
}
/**
......@@ -1229,6 +1032,7 @@ static int charger_manager_register_extcon(struct charger_manager *cm)
{
struct charger_desc *desc = cm->desc;
struct charger_regulator *charger;
unsigned long event;
int ret;
int i;
int j;
......@@ -1256,6 +1060,11 @@ static int charger_manager_register_extcon(struct charger_manager *cm)
}
cable->charger = charger;
cable->cm = cm;
event = extcon_get_state(cable->extcon_dev,
cable->extcon_type);
charger_extcon_notifier(&cable->nb,
event, NULL);
}
}
......@@ -1447,7 +1256,7 @@ static int cm_init_thermal_data(struct charger_manager *cm,
return PTR_ERR(cm->tzd_batt);
/* Use external thermometer */
properties[*num_properties] = POWER_SUPPLY_PROP_TEMP_AMBIENT;
properties[*num_properties] = POWER_SUPPLY_PROP_TEMP;
(*num_properties)++;
cm->desc->measure_battery_temp = true;
ret = 0;
......@@ -1491,8 +1300,6 @@ static struct charger_desc *of_cm_parse_desc(struct device *dev)
of_property_read_u32(np, "cm-poll-interval",
&desc->polling_interval_ms);
of_property_read_u32(np, "cm-fullbatt-vchkdrop-ms",
&desc->fullbatt_vchkdrop_ms);
of_property_read_u32(np, "cm-fullbatt-vchkdrop-volt",
&desc->fullbatt_vchkdrop_uV);
of_property_read_u32(np, "cm-fullbatt-voltage", &desc->fullbatt_uV);
......@@ -1504,8 +1311,8 @@ static struct charger_desc *of_cm_parse_desc(struct device *dev)
desc->battery_present = battery_stat;
/* chargers */
of_property_read_u32(np, "cm-num-chargers", &num_chgs);
if (num_chgs) {
num_chgs = of_property_count_strings(np, "cm-chargers");
if (num_chgs > 0) {
int i;
/* Allocate empty bin at the tail of array */
......@@ -1618,7 +1425,6 @@ static int charger_manager_probe(struct platform_device *pdev)
struct charger_desc *desc = cm_get_drv_data(pdev);
struct charger_manager *cm;
int ret, i = 0;
int j = 0;
union power_supply_propval val;
struct power_supply *fuel_gauge;
enum power_supply_property *properties;
......@@ -1654,9 +1460,8 @@ static int charger_manager_probe(struct platform_device *pdev)
if (desc->fullbatt_uV == 0) {
dev_info(&pdev->dev, "Ignoring full-battery voltage threshold as it is not supplied\n");
}
if (!desc->fullbatt_vchkdrop_ms || !desc->fullbatt_vchkdrop_uV) {
if (!desc->fullbatt_vchkdrop_uV) {
dev_info(&pdev->dev, "Disabling full-battery voltage drop checking mechanism as it is not supplied\n");
desc->fullbatt_vchkdrop_ms = 0;
desc->fullbatt_vchkdrop_uV = 0;
}
if (desc->fullbatt_soc == 0) {
......@@ -1738,6 +1543,12 @@ static int charger_manager_probe(struct platform_device *pdev)
desc->psy_fuel_gauge);
return -ENODEV;
}
if (!power_supply_get_property(fuel_gauge,
POWER_SUPPLY_PROP_CHARGE_FULL, &val)) {
properties[num_properties] =
POWER_SUPPLY_PROP_CHARGE_FULL;
num_properties++;
}
if (!power_supply_get_property(fuel_gauge,
POWER_SUPPLY_PROP_CHARGE_NOW, &val)) {
properties[num_properties] =
......@@ -1762,8 +1573,6 @@ static int charger_manager_probe(struct platform_device *pdev)
cm->charger_psy_desc.properties = properties;
cm->charger_psy_desc.num_properties = num_properties;
INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk);
/* Register sysfs entry for charger(regulator) */
ret = charger_manager_prepare_sysfs(cm);
if (ret < 0) {
......@@ -1813,19 +1622,8 @@ static int charger_manager_probe(struct platform_device *pdev)
return 0;
err_reg_extcon:
for (i = 0; i < desc->num_charger_regulators; i++) {
struct charger_regulator *charger;
charger = &desc->charger_regulators[i];
for (j = 0; j < charger->num_cables; j++) {
struct charger_cable *cable = &charger->cables[j];
/* Remove notifier block if only edev exists */
if (cable->extcon_dev.edev)
extcon_unregister_interest(&cable->extcon_dev);
}
for (i = 0; i < desc->num_charger_regulators; i++)
regulator_put(desc->charger_regulators[i].consumer);
}
power_supply_unregister(cm->charger_psy);
......@@ -1837,7 +1635,6 @@ static int charger_manager_remove(struct platform_device *pdev)
struct charger_manager *cm = platform_get_drvdata(pdev);
struct charger_desc *desc = cm->desc;
int i = 0;
int j = 0;
/* Remove from the list */
mutex_lock(&cm_list_mtx);
......@@ -1847,15 +1644,6 @@ static int charger_manager_remove(struct platform_device *pdev)
cancel_work_sync(&setup_polling);
cancel_delayed_work_sync(&cm_monitor_work);
for (i = 0 ; i < desc->num_charger_regulators ; i++) {
struct charger_regulator *charger
= &desc->charger_regulators[i];
for (j = 0 ; j < charger->num_cables ; j++) {
struct charger_cable *cable = &charger->cables[j];
extcon_unregister_interest(&cable->extcon_dev);
}
}
for (i = 0 ; i < desc->num_charger_regulators ; i++)
regulator_put(desc->charger_regulators[i].consumer);
......@@ -1903,8 +1691,6 @@ static bool cm_need_to_awake(void)
static int cm_suspend_prepare(struct device *dev)
{
struct charger_manager *cm = dev_get_drvdata(dev);
if (cm_need_to_awake())
return -EBUSY;
......@@ -1916,7 +1702,6 @@ static int cm_suspend_prepare(struct device *dev)
if (cm_timer_set) {
cancel_work_sync(&setup_polling);
cancel_delayed_work_sync(&cm_monitor_work);
cancel_delayed_work(&cm->fullbatt_vchk_work);
}
return 0;
......@@ -1941,31 +1726,6 @@ static void cm_suspend_complete(struct device *dev)
_cm_monitor(cm);
/* Re-enqueue delayed work (fullbatt_vchk_work) */
if (cm->fullbatt_vchk_jiffies_at) {
unsigned long delay = 0;
unsigned long now = jiffies + CM_JIFFIES_SMALL;
if (time_after_eq(now, cm->fullbatt_vchk_jiffies_at)) {
delay = (unsigned long)((long)now
- (long)(cm->fullbatt_vchk_jiffies_at));
delay = jiffies_to_msecs(delay);
} else {
delay = 0;
}
/*
* Account for cm_suspend_duration_ms with assuming that
* timer stops in suspend.
*/
if (delay > cm_suspend_duration_ms)
delay -= cm_suspend_duration_ms;
else
delay = 0;
queue_delayed_work(cm_wq, &cm->fullbatt_vchk_work,
msecs_to_jiffies(delay));
}
device_set_wakeup_capable(cm->dev, false);
}
......@@ -2007,56 +1767,6 @@ static void __exit charger_manager_cleanup(void)
}
module_exit(charger_manager_cleanup);
/**
* cm_notify_event - charger driver notify Charger Manager of charger event
* @psy: pointer to instance of charger's power_supply
* @type: type of charger event
* @msg: optional message passed to uevent_notify function
*/
void cm_notify_event(struct power_supply *psy, enum cm_event_types type,
char *msg)
{
struct charger_manager *cm;
bool found_power_supply = false;
if (psy == NULL)
return;
mutex_lock(&cm_list_mtx);
list_for_each_entry(cm, &cm_list, entry) {
if (match_string(cm->desc->psy_charger_stat, -1,
psy->desc->name) >= 0) {
found_power_supply = true;
break;
}
}
mutex_unlock(&cm_list_mtx);
if (!found_power_supply)
return;
switch (type) {
case CM_EVENT_BATT_FULL:
fullbatt_handler(cm);
break;
case CM_EVENT_BATT_OUT:
battout_handler(cm);
break;
case CM_EVENT_BATT_IN:
case CM_EVENT_EXT_PWR_IN_OUT ... CM_EVENT_CHG_START_STOP:
misc_event_handler(cm, type);
break;
case CM_EVENT_UNKNOWN:
case CM_EVENT_OTHERS:
uevent_notify(cm, msg ? msg : default_event_names[type]);
break;
default:
dev_err(cm->dev, "%s: type not specified\n", __func__);
break;
}
}
EXPORT_SYMBOL_GPL(cm_notify_event);
MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
MODULE_DESCRIPTION("Charger Manager");
MODULE_LICENSE("GPL");
......@@ -747,11 +747,8 @@ static int cpcap_battery_init_iio(struct cpcap_battery_ddata *ddata)
return 0;
out_err:
if (error != -EPROBE_DEFER)
dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n",
error);
return error;
return dev_err_probe(ddata->dev, error,
"could not initialize VBUS or ID IIO\n");
}
/* Calibrate coulomb counter */
......
......@@ -160,7 +160,7 @@ static int ds2780_get_voltage(struct ds2780_device_info *dev_info,
/*
* The voltage value is located in 10 bits across the voltage MSB
* and LSB registers in two's compliment form
* and LSB registers in two's complement form
* Sign bit of the voltage value is in bit 7 of the voltage MSB register
* Bits 9 - 3 of the voltage value are in bits 6 - 0 of the
* voltage MSB register
......@@ -188,7 +188,7 @@ static int ds2780_get_temperature(struct ds2780_device_info *dev_info,
/*
* The temperature value is located in 10 bits across the temperature
* MSB and LSB registers in two's compliment form
* MSB and LSB registers in two's complement form
* Sign bit of the temperature value is in bit 7 of the temperature
* MSB register
* Bits 9 - 3 of the temperature value are in bits 6 - 0 of the
......@@ -241,7 +241,7 @@ static int ds2780_get_current(struct ds2780_device_info *dev_info,
/*
* The current value is located in 16 bits across the current MSB
* and LSB registers in two's compliment form
* and LSB registers in two's complement form
* Sign bit of the current value is in bit 7 of the current MSB register
* Bits 14 - 8 of the current value are in bits 6 - 0 of the current
* MSB register
......
......@@ -168,7 +168,7 @@ static int ds2781_get_voltage(struct ds2781_device_info *dev_info,
return ret;
/*
* The voltage value is located in 10 bits across the voltage MSB
* and LSB registers in two's compliment form
* and LSB registers in two's complement form
* Sign bit of the voltage value is in bit 7 of the voltage MSB register
* Bits 9 - 3 of the voltage value are in bits 6 - 0 of the
* voltage MSB register
......@@ -197,7 +197,7 @@ static int ds2781_get_temperature(struct ds2781_device_info *dev_info,
return ret;
/*
* The temperature value is located in 10 bits across the temperature
* MSB and LSB registers in two's compliment form
* MSB and LSB registers in two's complement form
* Sign bit of the temperature value is in bit 7 of the temperature
* MSB register
* Bits 9 - 3 of the temperature value are in bits 6 - 0 of the
......@@ -242,7 +242,7 @@ static int ds2781_get_current(struct ds2781_device_info *dev_info,
/*
* The current value is located in 16 bits across the current MSB
* and LSB registers in two's compliment form
* and LSB registers in two's complement form
* Sign bit of the current value is in bit 7 of the current MSB register
* Bits 14 - 8 of the current value are in bits 6 - 0 of the current
* MSB register
......
......@@ -266,11 +266,13 @@ static const struct of_device_id goldfish_battery_of_match[] = {
};
MODULE_DEVICE_TABLE(of, goldfish_battery_of_match);
#ifdef CONFIG_ACPI
static const struct acpi_device_id goldfish_battery_acpi_match[] = {
{ "GFSH0001", 0 },
{ },
};
MODULE_DEVICE_TABLE(acpi, goldfish_battery_acpi_match);
#endif
static struct platform_driver goldfish_battery_device = {
.probe = goldfish_battery_probe,
......
......@@ -5,7 +5,6 @@
*/
#include <linux/device.h>
#include <linux/gpio.h> /* For legacy platform data */
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
......@@ -18,7 +17,13 @@
#include <linux/power/gpio-charger.h>
struct gpio_mapping {
u32 limit_ua;
u32 gpiodata;
} __packed;
struct gpio_charger {
struct device *dev;
unsigned int irq;
unsigned int charge_status_irq;
bool wakeup_enabled;
......@@ -27,6 +32,11 @@ struct gpio_charger {
struct power_supply_desc charger_desc;
struct gpio_desc *gpiod;
struct gpio_desc *charge_status;
struct gpio_descs *current_limit_gpios;
struct gpio_mapping *current_limit_map;
u32 current_limit_map_size;
u32 charge_current_limit;
};
static irqreturn_t gpio_charger_irq(int irq, void *devid)
......@@ -43,6 +53,35 @@ static inline struct gpio_charger *psy_to_gpio_charger(struct power_supply *psy)
return power_supply_get_drvdata(psy);
}
static int set_charge_current_limit(struct gpio_charger *gpio_charger, int val)
{
struct gpio_mapping mapping;
int ndescs = gpio_charger->current_limit_gpios->ndescs;
struct gpio_desc **gpios = gpio_charger->current_limit_gpios->desc;
int i;
if (!gpio_charger->current_limit_map_size)
return -EINVAL;
for (i = 0; i < gpio_charger->current_limit_map_size; i++) {
if (gpio_charger->current_limit_map[i].limit_ua <= val)
break;
}
mapping = gpio_charger->current_limit_map[i];
for (i = 0; i < ndescs; i++) {
bool val = (mapping.gpiodata >> i) & 1;
gpiod_set_value_cansleep(gpios[ndescs-i-1], val);
}
gpio_charger->charge_current_limit = mapping.limit_ua;
dev_dbg(gpio_charger->dev, "set charge current limit to %d (requested: %d)\n",
gpio_charger->charge_current_limit, val);
return 0;
}
static int gpio_charger_get_property(struct power_supply *psy,
enum power_supply_property psp, union power_supply_propval *val)
{
......@@ -58,6 +97,9 @@ static int gpio_charger_get_property(struct power_supply *psy,
else
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
val->intval = gpio_charger->charge_current_limit;
break;
default:
return -EINVAL;
}
......@@ -65,6 +107,34 @@ static int gpio_charger_get_property(struct power_supply *psy,
return 0;
}
static int gpio_charger_set_property(struct power_supply *psy,
enum power_supply_property psp, const union power_supply_propval *val)
{
struct gpio_charger *gpio_charger = psy_to_gpio_charger(psy);
switch (psp) {
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
return set_charge_current_limit(gpio_charger, val->intval);
default:
return -EINVAL;
}
return 0;
}
static int gpio_charger_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
return 1;
default:
break;
}
return 0;
}
static enum power_supply_type gpio_charger_get_type(struct device *dev)
{
const char *chargetype;
......@@ -112,6 +182,61 @@ static int gpio_charger_get_irq(struct device *dev, void *dev_id,
return irq;
}
static int init_charge_current_limit(struct device *dev,
struct gpio_charger *gpio_charger)
{
int i, len;
u32 cur_limit = U32_MAX;
gpio_charger->current_limit_gpios = devm_gpiod_get_array_optional(dev,
"charge-current-limit", GPIOD_OUT_LOW);
if (IS_ERR(gpio_charger->current_limit_gpios)) {
dev_err(dev, "error getting current-limit GPIOs\n");
return PTR_ERR(gpio_charger->current_limit_gpios);
}
if (!gpio_charger->current_limit_gpios)
return 0;
len = device_property_read_u32_array(dev, "charge-current-limit-mapping",
NULL, 0);
if (len < 0)
return len;
if (len == 0 || len % 2) {
dev_err(dev, "invalid charge-current-limit-mapping length\n");
return -EINVAL;
}
gpio_charger->current_limit_map = devm_kmalloc_array(dev,
len / 2, sizeof(*gpio_charger->current_limit_map), GFP_KERNEL);
if (!gpio_charger->current_limit_map)
return -ENOMEM;
gpio_charger->current_limit_map_size = len / 2;
len = device_property_read_u32_array(dev, "charge-current-limit-mapping",
(u32*) gpio_charger->current_limit_map, len);
if (len < 0)
return len;
for (i=0; i < gpio_charger->current_limit_map_size; i++) {
if (gpio_charger->current_limit_map[i].limit_ua > cur_limit) {
dev_err(dev, "charge-current-limit-mapping not sorted by current in descending order\n");
return -EINVAL;
}
cur_limit = gpio_charger->current_limit_map[i].limit_ua;
}
/* default to smallest current limitation for safety reasons */
len = gpio_charger->current_limit_map_size - 1;
set_charge_current_limit(gpio_charger,
gpio_charger->current_limit_map[len].limit_ua);
return 0;
}
/*
* The entries will be overwritten by driver's probe routine depending
* on the available features. This list ensures, that the array is big
......@@ -120,6 +245,7 @@ static int gpio_charger_get_irq(struct device *dev, void *dev_id,
static enum power_supply_property gpio_charger_properties[] = {
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
};
static int gpio_charger_probe(struct platform_device *pdev)
......@@ -131,7 +257,6 @@ static int gpio_charger_probe(struct platform_device *pdev)
struct power_supply_desc *charger_desc;
struct gpio_desc *charge_status;
int charge_status_irq;
unsigned long flags;
int ret;
int num_props = 0;
......@@ -143,40 +268,17 @@ static int gpio_charger_probe(struct platform_device *pdev)
gpio_charger = devm_kzalloc(dev, sizeof(*gpio_charger), GFP_KERNEL);
if (!gpio_charger)
return -ENOMEM;
gpio_charger->dev = dev;
/*
* This will fetch a GPIO descriptor from device tree, ACPI or
* boardfile descriptor tables. It's good to try this first.
*/
gpio_charger->gpiod = devm_gpiod_get_optional(dev, NULL, GPIOD_IN);
/*
* Fallback to legacy platform data method, if no GPIO is specified
* using boardfile descriptor tables.
*/
if (!gpio_charger->gpiod && pdata) {
/* Non-DT: use legacy GPIO numbers */
if (!gpio_is_valid(pdata->gpio)) {
dev_err(dev, "Invalid gpio pin in pdata\n");
return -EINVAL;
}
flags = GPIOF_IN;
if (pdata->gpio_active_low)
flags |= GPIOF_ACTIVE_LOW;
ret = devm_gpio_request_one(dev, pdata->gpio, flags,
dev_name(dev));
if (ret) {
dev_err(dev, "Failed to request gpio pin: %d\n", ret);
return ret;
}
/* Then convert this to gpiod for now */
gpio_charger->gpiod = gpio_to_desc(pdata->gpio);
} else if (IS_ERR(gpio_charger->gpiod)) {
if (IS_ERR(gpio_charger->gpiod)) {
/* Just try again if this happens */
if (PTR_ERR(gpio_charger->gpiod) == -EPROBE_DEFER)
return -EPROBE_DEFER;
dev_err(dev, "error getting GPIO descriptor\n");
return PTR_ERR(gpio_charger->gpiod);
return dev_err_probe(dev, PTR_ERR(gpio_charger->gpiod),
"error getting GPIO descriptor\n");
}
if (gpio_charger->gpiod) {
......@@ -193,10 +295,22 @@ static int gpio_charger_probe(struct platform_device *pdev)
num_props++;
}
ret = init_charge_current_limit(dev, gpio_charger);
if (ret < 0)
return ret;
if (gpio_charger->current_limit_map) {
gpio_charger_properties[num_props] =
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX;
num_props++;
}
charger_desc = &gpio_charger->charger_desc;
charger_desc->properties = gpio_charger_properties;
charger_desc->num_properties = num_props;
charger_desc->get_property = gpio_charger_get_property;
charger_desc->set_property = gpio_charger_set_property;
charger_desc->property_is_writeable =
gpio_charger_property_is_writeable;
psy_cfg.of_node = dev->of_node;
psy_cfg.drv_data = gpio_charger;
......
......@@ -147,11 +147,9 @@ static int ingenic_battery_probe(struct platform_device *pdev)
psy_cfg.of_node = dev->of_node;
bat->battery = devm_power_supply_register(dev, desc, &psy_cfg);
if (IS_ERR(bat->battery)) {
if (PTR_ERR(bat->battery) != -EPROBE_DEFER)
dev_err(dev, "Unable to register battery\n");
return PTR_ERR(bat->battery);
}
if (IS_ERR(bat->battery))
return dev_err_probe(dev, PTR_ERR(bat->battery),
"Unable to register battery\n");
ret = power_supply_get_battery_info(bat->battery, &bat->info);
if (ret) {
......
......@@ -166,27 +166,21 @@ static int lego_ev3_battery_probe(struct platform_device *pdev)
batt->iio_v = devm_iio_channel_get(dev, "voltage");
err = PTR_ERR_OR_ZERO(batt->iio_v);
if (err) {
if (err != -EPROBE_DEFER)
dev_err(dev, "Failed to get voltage iio channel\n");
return err;
}
if (err)
return dev_err_probe(dev, err,
"Failed to get voltage iio channel\n");
batt->iio_i = devm_iio_channel_get(dev, "current");
err = PTR_ERR_OR_ZERO(batt->iio_i);
if (err) {
if (err != -EPROBE_DEFER)
dev_err(dev, "Failed to get current iio channel\n");
return err;
}
if (err)
return dev_err_probe(dev, err,
"Failed to get current iio channel\n");
batt->rechargeable_gpio = devm_gpiod_get(dev, "rechargeable", GPIOD_IN);
err = PTR_ERR_OR_ZERO(batt->rechargeable_gpio);
if (err) {
if (err != -EPROBE_DEFER)
dev_err(dev, "Failed to get rechargeable gpio\n");
return err;
}
if (err)
return dev_err_probe(dev, err,
"Failed to get rechargeable gpio\n");
/*
* The rechargeable battery indication switch cannot be changed without
......
......@@ -473,7 +473,8 @@ static int ltc294x_i2c_probe(struct i2c_client *client,
np = of_node_get(client->dev.of_node);
info->id = (enum ltc294x_id)of_device_get_match_data(&client->dev);
info->id = (enum ltc294x_id) (uintptr_t) of_device_get_match_data(
&client->dev);
info->supply_desc.name = np->name;
/* r_sense can be negative, when sense+ is connected to the battery
......
......@@ -15,196 +15,289 @@
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/power_supply.h>
#include <linux/of_device.h>
#include <linux/max17040_battery.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#define MAX17040_VCELL 0x02
#define MAX17040_SOC 0x04
#define MAX17040_MODE 0x06
#define MAX17040_VER 0x08
#define MAX17040_RCOMP 0x0C
#define MAX17040_CONFIG 0x0C
#define MAX17040_STATUS 0x1A
#define MAX17040_CMD 0xFE
#define MAX17040_DELAY 1000
#define MAX17040_BATTERY_FULL 95
#define MAX17040_RCOMP_DEFAULT 0x9700
#define MAX17040_ATHD_MASK 0xFFC0
#define MAX17040_ATHD_MASK 0x3f
#define MAX17040_ALSC_MASK 0x40
#define MAX17040_ATHD_DEFAULT_POWER_UP 4
#define MAX17040_STATUS_HD_MASK 0x1000
#define MAX17040_STATUS_SC_MASK 0x2000
#define MAX17040_CFG_RCOMP_MASK 0xff00
enum chip_id {
ID_MAX17040,
ID_MAX17041,
ID_MAX17043,
ID_MAX17044,
ID_MAX17048,
ID_MAX17049,
ID_MAX17058,
ID_MAX17059,
};
/* values that differ by chip_id */
struct chip_data {
u16 reset_val;
u16 vcell_shift;
u16 vcell_mul;
u16 vcell_div;
u8 has_low_soc_alert;
u8 rcomp_bytes;
u8 has_soc_alert;
};
static struct chip_data max17040_family[] = {
[ID_MAX17040] = {
.reset_val = 0x0054,
.vcell_shift = 4,
.vcell_mul = 1250,
.vcell_div = 1,
.has_low_soc_alert = 0,
.rcomp_bytes = 2,
.has_soc_alert = 0,
},
[ID_MAX17041] = {
.reset_val = 0x0054,
.vcell_shift = 4,
.vcell_mul = 2500,
.vcell_div = 1,
.has_low_soc_alert = 0,
.rcomp_bytes = 2,
.has_soc_alert = 0,
},
[ID_MAX17043] = {
.reset_val = 0x0054,
.vcell_shift = 4,
.vcell_mul = 1250,
.vcell_div = 1,
.has_low_soc_alert = 1,
.rcomp_bytes = 1,
.has_soc_alert = 0,
},
[ID_MAX17044] = {
.reset_val = 0x0054,
.vcell_shift = 4,
.vcell_mul = 2500,
.vcell_div = 1,
.has_low_soc_alert = 1,
.rcomp_bytes = 1,
.has_soc_alert = 0,
},
[ID_MAX17048] = {
.reset_val = 0x5400,
.vcell_shift = 0,
.vcell_mul = 625,
.vcell_div = 8,
.has_low_soc_alert = 1,
.rcomp_bytes = 1,
.has_soc_alert = 1,
},
[ID_MAX17049] = {
.reset_val = 0x5400,
.vcell_shift = 0,
.vcell_mul = 625,
.vcell_div = 4,
.has_low_soc_alert = 1,
.rcomp_bytes = 1,
.has_soc_alert = 1,
},
[ID_MAX17058] = {
.reset_val = 0x5400,
.vcell_shift = 0,
.vcell_mul = 625,
.vcell_div = 8,
.has_low_soc_alert = 1,
.rcomp_bytes = 1,
.has_soc_alert = 0,
},
[ID_MAX17059] = {
.reset_val = 0x5400,
.vcell_shift = 0,
.vcell_mul = 625,
.vcell_div = 4,
.has_low_soc_alert = 1,
.rcomp_bytes = 1,
.has_soc_alert = 0,
},
};
struct max17040_chip {
struct i2c_client *client;
struct regmap *regmap;
struct delayed_work work;
struct power_supply *battery;
struct max17040_platform_data *pdata;
struct chip_data data;
/* State Of Connect */
int online;
/* battery voltage */
int vcell;
/* battery capacity */
int soc;
/* State Of Charge */
int status;
/* Low alert threshold from 32% to 1% of the State of Charge */
u32 low_soc_alert;
/* some devices return twice the capacity */
bool quirk_double_soc;
/* higher 8 bits for 17043+, 16 bits for 17040,41 */
u16 rcomp;
};
static int max17040_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
static int max17040_reset(struct max17040_chip *chip)
{
struct max17040_chip *chip = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = chip->status;
break;
case POWER_SUPPLY_PROP_ONLINE:
val->intval = chip->online;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = chip->vcell;
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = chip->soc;
break;
case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
val->intval = chip->low_soc_alert;
break;
default:
return -EINVAL;
}
return 0;
return regmap_write(chip->regmap, MAX17040_CMD, chip->data.reset_val);
}
static int max17040_write_reg(struct i2c_client *client, int reg, u16 value)
static int max17040_set_low_soc_alert(struct max17040_chip *chip, u32 level)
{
int ret;
ret = i2c_smbus_write_word_swapped(client, reg, value);
if (ret < 0)
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
return ret;
level = 32 - level * (chip->quirk_double_soc ? 2 : 1);
return regmap_update_bits(chip->regmap, MAX17040_CONFIG,
MAX17040_ATHD_MASK, level);
}
static int max17040_read_reg(struct i2c_client *client, int reg)
static int max17040_set_soc_alert(struct max17040_chip *chip, bool enable)
{
int ret;
ret = i2c_smbus_read_word_swapped(client, reg);
if (ret < 0)
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
return ret;
return regmap_update_bits(chip->regmap, MAX17040_CONFIG,
MAX17040_ALSC_MASK, enable ? MAX17040_ALSC_MASK : 0);
}
static void max17040_reset(struct i2c_client *client)
static int max17040_set_rcomp(struct max17040_chip *chip, u16 rcomp)
{
max17040_write_reg(client, MAX17040_CMD, 0x0054);
u16 mask = chip->data.rcomp_bytes == 2 ?
0xffff : MAX17040_CFG_RCOMP_MASK;
return regmap_update_bits(chip->regmap, MAX17040_CONFIG, mask, rcomp);
}
static int max17040_set_low_soc_alert(struct i2c_client *client, u32 level)
static int max17040_raw_vcell_to_uvolts(struct max17040_chip *chip, u16 vcell)
{
int ret;
u16 data;
struct chip_data *d = &chip->data;
level = 32 - level;
data = max17040_read_reg(client, MAX17040_RCOMP);
/* clear the alrt bit and set LSb 5 bits */
data &= MAX17040_ATHD_MASK;
data |= level;
ret = max17040_write_reg(client, MAX17040_RCOMP, data);
return ret;
return (vcell >> d->vcell_shift) * d->vcell_mul / d->vcell_div;
}
static void max17040_get_vcell(struct i2c_client *client)
static int max17040_get_vcell(struct max17040_chip *chip)
{
struct max17040_chip *chip = i2c_get_clientdata(client);
u16 vcell;
u32 vcell;
vcell = max17040_read_reg(client, MAX17040_VCELL);
regmap_read(chip->regmap, MAX17040_VCELL, &vcell);
chip->vcell = (vcell >> 4) * 1250;
return max17040_raw_vcell_to_uvolts(chip, vcell);
}
static void max17040_get_soc(struct i2c_client *client)
static int max17040_get_soc(struct max17040_chip *chip)
{
struct max17040_chip *chip = i2c_get_clientdata(client);
u16 soc;
u32 soc;
soc = max17040_read_reg(client, MAX17040_SOC);
regmap_read(chip->regmap, MAX17040_SOC, &soc);
chip->soc = (soc >> 8);
return soc >> (chip->quirk_double_soc ? 9 : 8);
}
static void max17040_get_version(struct i2c_client *client)
static int max17040_get_version(struct max17040_chip *chip)
{
u16 version;
int ret;
u32 version;
version = max17040_read_reg(client, MAX17040_VER);
ret = regmap_read(chip->regmap, MAX17040_VER, &version);
dev_info(&client->dev, "MAX17040 Fuel-Gauge Ver 0x%x\n", version);
return ret ? ret : version;
}
static void max17040_get_online(struct i2c_client *client)
static int max17040_get_online(struct max17040_chip *chip)
{
struct max17040_chip *chip = i2c_get_clientdata(client);
if (chip->pdata && chip->pdata->battery_online)
chip->online = chip->pdata->battery_online();
else
chip->online = 1;
return chip->pdata && chip->pdata->battery_online ?
chip->pdata->battery_online() : 1;
}
static void max17040_get_status(struct i2c_client *client)
static int max17040_get_status(struct max17040_chip *chip)
{
struct max17040_chip *chip = i2c_get_clientdata(client);
if (!chip->pdata || !chip->pdata->charger_online
|| !chip->pdata->charger_enable) {
chip->status = POWER_SUPPLY_STATUS_UNKNOWN;
return;
}
|| !chip->pdata->charger_enable)
return POWER_SUPPLY_STATUS_UNKNOWN;
if (chip->pdata->charger_online()) {
if (max17040_get_soc(chip) > MAX17040_BATTERY_FULL)
return POWER_SUPPLY_STATUS_FULL;
if (chip->pdata->charger_online())
if (chip->pdata->charger_enable())
chip->status = POWER_SUPPLY_STATUS_CHARGING;
return POWER_SUPPLY_STATUS_CHARGING;
else
chip->status = POWER_SUPPLY_STATUS_NOT_CHARGING;
} else {
chip->status = POWER_SUPPLY_STATUS_DISCHARGING;
}
if (chip->soc > MAX17040_BATTERY_FULL)
chip->status = POWER_SUPPLY_STATUS_FULL;
return POWER_SUPPLY_STATUS_NOT_CHARGING;
else
return POWER_SUPPLY_STATUS_DISCHARGING;
}
static int max17040_get_of_data(struct max17040_chip *chip)
{
struct device *dev = &chip->client->dev;
struct chip_data *data = &max17040_family[
(uintptr_t) of_device_get_match_data(dev)];
int rcomp_len;
u8 rcomp[2];
chip->quirk_double_soc = device_property_read_bool(dev,
"maxim,double-soc");
chip->low_soc_alert = MAX17040_ATHD_DEFAULT_POWER_UP;
device_property_read_u32(dev,
"maxim,alert-low-soc-level",
&chip->low_soc_alert);
if (chip->low_soc_alert <= 0 || chip->low_soc_alert >= 33)
if (chip->low_soc_alert <= 0 ||
chip->low_soc_alert > (chip->quirk_double_soc ? 16 : 32)) {
dev_err(dev, "maxim,alert-low-soc-level out of bounds\n");
return -EINVAL;
}
rcomp_len = device_property_count_u8(dev, "maxim,rcomp");
chip->rcomp = MAX17040_RCOMP_DEFAULT;
if (rcomp_len == data->rcomp_bytes) {
device_property_read_u8_array(dev, "maxim,rcomp",
rcomp, rcomp_len);
chip->rcomp = rcomp_len == 2 ?
rcomp[0] << 8 | rcomp[1] :
rcomp[0] << 8;
} else if (rcomp_len > 0) {
dev_err(dev, "maxim,rcomp has incorrect length\n");
return -EINVAL;
}
return 0;
}
static void max17040_check_changes(struct i2c_client *client)
static void max17040_check_changes(struct max17040_chip *chip)
{
chip->soc = max17040_get_soc(chip);
chip->status = max17040_get_status(chip);
}
static void max17040_queue_work(struct max17040_chip *chip)
{
queue_delayed_work(system_power_efficient_wq, &chip->work,
MAX17040_DELAY);
}
static void max17040_stop_work(void *data)
{
max17040_get_vcell(client);
max17040_get_soc(client);
max17040_get_online(client);
max17040_get_status(client);
struct max17040_chip *chip = data;
cancel_delayed_work_sync(&chip->work);
}
static void max17040_work(struct work_struct *work)
......@@ -217,30 +310,51 @@ static void max17040_work(struct work_struct *work)
/* store SOC and status to check changes */
last_soc = chip->soc;
last_status = chip->status;
max17040_check_changes(chip->client);
max17040_check_changes(chip);
/* check changes and send uevent */
if (last_soc != chip->soc || last_status != chip->status)
power_supply_changed(chip->battery);
queue_delayed_work(system_power_efficient_wq, &chip->work,
MAX17040_DELAY);
max17040_queue_work(chip);
}
/* Returns true if alert cause was SOC change, not low SOC */
static bool max17040_handle_soc_alert(struct max17040_chip *chip)
{
bool ret = true;
u32 data;
regmap_read(chip->regmap, MAX17040_STATUS, &data);
if (data & MAX17040_STATUS_HD_MASK) {
// this alert was caused by low soc
ret = false;
}
if (data & MAX17040_STATUS_SC_MASK) {
// soc change bit -- deassert to mark as handled
regmap_write(chip->regmap, MAX17040_STATUS,
data & ~MAX17040_STATUS_SC_MASK);
}
return ret;
}
static irqreturn_t max17040_thread_handler(int id, void *dev)
{
struct max17040_chip *chip = dev;
struct i2c_client *client = chip->client;
dev_warn(&client->dev, "IRQ: Alert battery low level");
if (!(chip->data.has_soc_alert && max17040_handle_soc_alert(chip)))
dev_warn(&chip->client->dev, "IRQ: Alert battery low level\n");
/* read registers */
max17040_check_changes(chip->client);
max17040_check_changes(chip);
/* send uevent */
power_supply_changed(chip->battery);
/* reset alert bit */
max17040_set_low_soc_alert(client, chip->low_soc_alert);
max17040_set_low_soc_alert(chip, chip->low_soc_alert);
return IRQ_HANDLED;
}
......@@ -279,12 +393,13 @@ static int max17040_set_property(struct power_supply *psy,
switch (psp) {
case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
/* alert threshold can be programmed from 1% up to 32% */
if ((val->intval < 1) || (val->intval > 32)) {
/* alert threshold can be programmed from 1% up to 16/32% */
if ((val->intval < 1) ||
(val->intval > (chip->quirk_double_soc ? 16 : 32))) {
ret = -EINVAL;
break;
}
ret = max17040_set_low_soc_alert(chip->client, val->intval);
ret = max17040_set_low_soc_alert(chip, val->intval);
chip->low_soc_alert = val->intval;
break;
default:
......@@ -294,6 +409,41 @@ static int max17040_set_property(struct power_supply *psy,
return ret;
}
static int max17040_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct max17040_chip *chip = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = max17040_get_status(chip);
break;
case POWER_SUPPLY_PROP_ONLINE:
val->intval = max17040_get_online(chip);
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = max17040_get_vcell(chip);
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = max17040_get_soc(chip);
break;
case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
val->intval = chip->low_soc_alert;
break;
default:
return -EINVAL;
}
return 0;
}
static const struct regmap_config max17040_regmap = {
.reg_bits = 8,
.reg_stride = 2,
.val_bits = 16,
.val_format_endian = REGMAP_ENDIAN_BIG,
};
static enum power_supply_property max17040_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
......@@ -318,6 +468,8 @@ static int max17040_probe(struct i2c_client *client,
struct i2c_adapter *adapter = client->adapter;
struct power_supply_config psy_cfg = {};
struct max17040_chip *chip;
enum chip_id chip_id;
bool enable_irq = false;
int ret;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
......@@ -328,37 +480,68 @@ static int max17040_probe(struct i2c_client *client,
return -ENOMEM;
chip->client = client;
chip->regmap = devm_regmap_init_i2c(client, &max17040_regmap);
chip->pdata = client->dev.platform_data;
chip_id = (enum chip_id) id->driver_data;
if (client->dev.of_node) {
ret = max17040_get_of_data(chip);
if (ret) {
dev_err(&client->dev,
"failed: low SOC alert OF data out of bounds\n");
if (ret)
return ret;
chip_id = (enum chip_id) (uintptr_t)
of_device_get_match_data(&client->dev);
}
chip->data = max17040_family[chip_id];
i2c_set_clientdata(client, chip);
psy_cfg.drv_data = chip;
chip->battery = power_supply_register(&client->dev,
chip->battery = devm_power_supply_register(&client->dev,
&max17040_battery_desc, &psy_cfg);
if (IS_ERR(chip->battery)) {
dev_err(&client->dev, "failed: power supply register\n");
return PTR_ERR(chip->battery);
}
max17040_reset(client);
max17040_get_version(client);
ret = max17040_get_version(chip);
if (ret < 0)
return ret;
dev_dbg(&chip->client->dev, "MAX17040 Fuel-Gauge Ver 0x%x\n", ret);
if (chip_id == ID_MAX17040 || chip_id == ID_MAX17041)
max17040_reset(chip);
max17040_set_rcomp(chip, chip->rcomp);
/* check interrupt */
if (client->irq && of_device_is_compatible(client->dev.of_node,
"maxim,max77836-battery")) {
ret = max17040_set_low_soc_alert(client, chip->low_soc_alert);
if (client->irq && chip->data.has_low_soc_alert) {
ret = max17040_set_low_soc_alert(chip, chip->low_soc_alert);
if (ret) {
dev_err(&client->dev,
"Failed to set low SOC alert: err %d\n", ret);
return ret;
}
enable_irq = true;
}
if (client->irq && chip->data.has_soc_alert) {
ret = max17040_set_soc_alert(chip, 1);
if (ret) {
dev_err(&client->dev,
"Failed to set SOC alert: err %d\n", ret);
return ret;
}
enable_irq = true;
} else {
/* soc alerts negate the need for polling */
INIT_DEFERRABLE_WORK(&chip->work, max17040_work);
ret = devm_add_action(&client->dev, max17040_stop_work, chip);
if (ret)
return ret;
max17040_queue_work(chip);
}
if (enable_irq) {
ret = max17040_enable_alert_irq(chip);
if (ret) {
client->irq = 0;
......@@ -367,19 +550,6 @@ static int max17040_probe(struct i2c_client *client,
}
}
INIT_DEFERRABLE_WORK(&chip->work, max17040_work);
queue_delayed_work(system_power_efficient_wq, &chip->work,
MAX17040_DELAY);
return 0;
}
static int max17040_remove(struct i2c_client *client)
{
struct max17040_chip *chip = i2c_get_clientdata(client);
power_supply_unregister(chip->battery);
cancel_delayed_work(&chip->work);
return 0;
}
......@@ -390,6 +560,10 @@ static int max17040_suspend(struct device *dev)
struct i2c_client *client = to_i2c_client(dev);
struct max17040_chip *chip = i2c_get_clientdata(client);
if (client->irq && chip->data.has_soc_alert)
// disable soc alert to prevent wakeup
max17040_set_soc_alert(chip, 0);
else
cancel_delayed_work(&chip->work);
if (client->irq && device_may_wakeup(dev))
......@@ -403,12 +577,14 @@ static int max17040_resume(struct device *dev)
struct i2c_client *client = to_i2c_client(dev);
struct max17040_chip *chip = i2c_get_clientdata(client);
queue_delayed_work(system_power_efficient_wq, &chip->work,
MAX17040_DELAY);
if (client->irq && device_may_wakeup(dev))
disable_irq_wake(client->irq);
if (client->irq && chip->data.has_soc_alert)
max17040_set_soc_alert(chip, 1);
else
max17040_queue_work(chip);
return 0;
}
......@@ -422,16 +598,30 @@ static SIMPLE_DEV_PM_OPS(max17040_pm_ops, max17040_suspend, max17040_resume);
#endif /* CONFIG_PM_SLEEP */
static const struct i2c_device_id max17040_id[] = {
{ "max17040" },
{ "max77836-battery" },
{ }
{ "max17040", ID_MAX17040 },
{ "max17041", ID_MAX17041 },
{ "max17043", ID_MAX17043 },
{ "max77836-battery", ID_MAX17043 },
{ "max17044", ID_MAX17044 },
{ "max17048", ID_MAX17048 },
{ "max17049", ID_MAX17049 },
{ "max17058", ID_MAX17058 },
{ "max17059", ID_MAX17059 },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, max17040_id);
static const struct of_device_id max17040_of_match[] = {
{ .compatible = "maxim,max17040" },
{ .compatible = "maxim,max77836-battery" },
{ },
{ .compatible = "maxim,max17040", .data = (void *) ID_MAX17040 },
{ .compatible = "maxim,max17041", .data = (void *) ID_MAX17041 },
{ .compatible = "maxim,max17043", .data = (void *) ID_MAX17043 },
{ .compatible = "maxim,max77836-battery", .data = (void *) ID_MAX17043 },
{ .compatible = "maxim,max17044", .data = (void *) ID_MAX17044 },
{ .compatible = "maxim,max17048", .data = (void *) ID_MAX17048 },
{ .compatible = "maxim,max17049", .data = (void *) ID_MAX17049 },
{ .compatible = "maxim,max17058", .data = (void *) ID_MAX17058 },
{ .compatible = "maxim,max17059", .data = (void *) ID_MAX17059 },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, max17040_of_match);
......@@ -442,7 +632,6 @@ static struct i2c_driver max17040_i2c_driver = {
.pm = MAX17040_PM_OPS,
},
.probe = max17040_probe,
.remove = max17040_remove,
.id_table = max17040_id,
};
module_i2c_driver(max17040_i2c_driver);
......
......@@ -104,11 +104,6 @@ static int pm2xxx_charger_current_map[] = {
3000,
};
static const struct i2c_device_id pm2xxx_ident[] = {
{ "pm2301", 0 },
{ }
};
static void set_lpn_pin(struct pm2xxx_charger *pm2)
{
if (!pm2->ac.charger_connected && gpio_is_valid(pm2->lpn_pin)) {
......@@ -396,7 +391,7 @@ static int pm2_int_reg3(void *pm2_data, int val)
if (val & (PM2XXX_INT4_ITCHARGINGON)) {
dev_dbg(pm2->dev ,
"chargind operation has started\n");
"charging operation has started\n");
}
if (val & (PM2XXX_INT4_ITVRESUME)) {
......
......@@ -579,6 +579,12 @@ int power_supply_get_battery_info(struct power_supply *psy,
info->charge_term_current_ua = -EINVAL;
info->constant_charge_current_max_ua = -EINVAL;
info->constant_charge_voltage_max_uv = -EINVAL;
info->temp_ambient_alert_min = INT_MIN;
info->temp_ambient_alert_max = INT_MAX;
info->temp_alert_min = INT_MIN;
info->temp_alert_max = INT_MAX;
info->temp_min = INT_MIN;
info->temp_max = INT_MAX;
info->factory_internal_resistance_uohm = -EINVAL;
info->resist_table = NULL;
......@@ -639,6 +645,19 @@ int power_supply_get_battery_info(struct power_supply *psy,
of_property_read_u32(battery_np, "factory-internal-resistance-micro-ohms",
&info->factory_internal_resistance_uohm);
of_property_read_u32_index(battery_np, "ambient-celsius",
0, &info->temp_ambient_alert_min);
of_property_read_u32_index(battery_np, "ambient-celsius",
1, &info->temp_ambient_alert_max);
of_property_read_u32_index(battery_np, "alert-celsius",
0, &info->temp_alert_min);
of_property_read_u32_index(battery_np, "alert-celsius",
1, &info->temp_alert_max);
of_property_read_u32_index(battery_np, "operating-range-celsius",
0, &info->temp_min);
of_property_read_u32_index(battery_np, "operating-range-celsius",
1, &info->temp_max);
len = of_property_count_u32_elems(battery_np, "ocv-capacity-celsius");
if (len < 0 && len != -EINVAL) {
err = len;
......
......@@ -56,6 +56,7 @@ static const char * const POWER_SUPPLY_TYPE_TEXT[] = {
[POWER_SUPPLY_TYPE_USB_PD] = "USB_PD",
[POWER_SUPPLY_TYPE_USB_PD_DRP] = "USB_PD_DRP",
[POWER_SUPPLY_TYPE_APPLE_BRICK_ID] = "BrickID",
[POWER_SUPPLY_TYPE_WIRELESS] = "Wireless",
};
static const char * const POWER_SUPPLY_USB_TYPE_TEXT[] = {
......
// SPDX-License-Identifier: GPL-2.0+
/*
* Power supply driver for the RICOH RN5T618 power management chip family
*
* Copyright (C) 2020 Andreas Kemnade
*/
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/bitops.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/mfd/rn5t618.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#define CHG_STATE_ADP_INPUT 0x40
#define CHG_STATE_USB_INPUT 0x80
#define CHG_STATE_MASK 0x1f
#define CHG_STATE_CHG_OFF 0
#define CHG_STATE_CHG_READY_VADP 1
#define CHG_STATE_CHG_TRICKLE 2
#define CHG_STATE_CHG_RAPID 3
#define CHG_STATE_CHG_COMPLETE 4
#define CHG_STATE_SUSPEND 5
#define CHG_STATE_VCHG_OVER_VOL 6
#define CHG_STATE_BAT_ERROR 7
#define CHG_STATE_NO_BAT 8
#define CHG_STATE_BAT_OVER_VOL 9
#define CHG_STATE_BAT_TEMP_ERR 10
#define CHG_STATE_DIE_ERR 11
#define CHG_STATE_DIE_SHUTDOWN 12
#define CHG_STATE_NO_BAT2 13
#define CHG_STATE_CHG_READY_VUSB 14
#define FG_ENABLE 1
struct rn5t618_power_info {
struct rn5t618 *rn5t618;
struct platform_device *pdev;
struct power_supply *battery;
struct power_supply *usb;
struct power_supply *adp;
int irq;
};
static enum power_supply_property rn5t618_usb_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
};
static enum power_supply_property rn5t618_adp_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
};
static enum power_supply_property rn5t618_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW,
};
static int rn5t618_battery_read_doublereg(struct rn5t618_power_info *info,
u8 reg, u16 *result)
{
int ret, i;
u8 data[2];
u16 old, new;
old = 0;
/* Prevent races when registers are changing. */
for (i = 0; i < 3; i++) {
ret = regmap_bulk_read(info->rn5t618->regmap,
reg, data, sizeof(data));
if (ret)
return ret;
new = data[0] << 8;
new |= data[1];
if (new == old)
break;
old = new;
}
*result = new;
return 0;
}
static int rn5t618_decode_status(unsigned int status)
{
switch (status & CHG_STATE_MASK) {
case CHG_STATE_CHG_OFF:
case CHG_STATE_SUSPEND:
case CHG_STATE_VCHG_OVER_VOL:
case CHG_STATE_DIE_SHUTDOWN:
return POWER_SUPPLY_STATUS_DISCHARGING;
case CHG_STATE_CHG_TRICKLE:
case CHG_STATE_CHG_RAPID:
return POWER_SUPPLY_STATUS_CHARGING;
case CHG_STATE_CHG_COMPLETE:
return POWER_SUPPLY_STATUS_FULL;
default:
return POWER_SUPPLY_STATUS_NOT_CHARGING;
}
}
static int rn5t618_battery_status(struct rn5t618_power_info *info,
union power_supply_propval *val)
{
unsigned int v;
int ret;
ret = regmap_read(info->rn5t618->regmap, RN5T618_CHGSTATE, &v);
if (ret)
return ret;
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
if (v & 0xc0) { /* USB or ADP plugged */
val->intval = rn5t618_decode_status(v);
} else
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
return ret;
}
static int rn5t618_battery_present(struct rn5t618_power_info *info,
union power_supply_propval *val)
{
unsigned int v;
int ret;
ret = regmap_read(info->rn5t618->regmap, RN5T618_CHGSTATE, &v);
if (ret)
return ret;
v &= CHG_STATE_MASK;
if ((v == CHG_STATE_NO_BAT) || (v == CHG_STATE_NO_BAT2))
val->intval = 0;
else
val->intval = 1;
return ret;
}
static int rn5t618_battery_voltage_now(struct rn5t618_power_info *info,
union power_supply_propval *val)
{
u16 res;
int ret;
ret = rn5t618_battery_read_doublereg(info, RN5T618_VOLTAGE_1, &res);
if (ret)
return ret;
val->intval = res * 2 * 2500 / 4095 * 1000;
return 0;
}
static int rn5t618_battery_current_now(struct rn5t618_power_info *info,
union power_supply_propval *val)
{
u16 res;
int ret;
ret = rn5t618_battery_read_doublereg(info, RN5T618_CC_AVEREG1, &res);
if (ret)
return ret;
/* current is negative when discharging */
val->intval = sign_extend32(res, 13) * 1000;
return 0;
}
static int rn5t618_battery_capacity(struct rn5t618_power_info *info,
union power_supply_propval *val)
{
unsigned int v;
int ret;
ret = regmap_read(info->rn5t618->regmap, RN5T618_SOC, &v);
if (ret)
return ret;
val->intval = v;
return 0;
}
static int rn5t618_battery_temp(struct rn5t618_power_info *info,
union power_supply_propval *val)
{
u16 res;
int ret;
ret = rn5t618_battery_read_doublereg(info, RN5T618_TEMP_1, &res);
if (ret)
return ret;
val->intval = sign_extend32(res, 11) * 10 / 16;
return 0;
}
static int rn5t618_battery_tte(struct rn5t618_power_info *info,
union power_supply_propval *val)
{
u16 res;
int ret;
ret = rn5t618_battery_read_doublereg(info, RN5T618_TT_EMPTY_H, &res);
if (ret)
return ret;
if (res == 65535)
return -ENODATA;
val->intval = res * 60;
return 0;
}
static int rn5t618_battery_ttf(struct rn5t618_power_info *info,
union power_supply_propval *val)
{
u16 res;
int ret;
ret = rn5t618_battery_read_doublereg(info, RN5T618_TT_FULL_H, &res);
if (ret)
return ret;
if (res == 65535)
return -ENODATA;
val->intval = res * 60;
return 0;
}
static int rn5t618_battery_charge_full(struct rn5t618_power_info *info,
union power_supply_propval *val)
{
u16 res;
int ret;
ret = rn5t618_battery_read_doublereg(info, RN5T618_FA_CAP_H, &res);
if (ret)
return ret;
val->intval = res * 1000;
return 0;
}
static int rn5t618_battery_charge_now(struct rn5t618_power_info *info,
union power_supply_propval *val)
{
u16 res;
int ret;
ret = rn5t618_battery_read_doublereg(info, RN5T618_RE_CAP_H, &res);
if (ret)
return ret;
val->intval = res * 1000;
return 0;
}
static int rn5t618_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
int ret = 0;
struct rn5t618_power_info *info = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
ret = rn5t618_battery_status(info, val);
break;
case POWER_SUPPLY_PROP_PRESENT:
ret = rn5t618_battery_present(info, val);
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
ret = rn5t618_battery_voltage_now(info, val);
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
ret = rn5t618_battery_current_now(info, val);
break;
case POWER_SUPPLY_PROP_CAPACITY:
ret = rn5t618_battery_capacity(info, val);
break;
case POWER_SUPPLY_PROP_TEMP:
ret = rn5t618_battery_temp(info, val);
break;
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
ret = rn5t618_battery_tte(info, val);
break;
case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
ret = rn5t618_battery_ttf(info, val);
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
ret = rn5t618_battery_charge_full(info, val);
break;
case POWER_SUPPLY_PROP_CHARGE_NOW:
ret = rn5t618_battery_charge_now(info, val);
break;
default:
return -EINVAL;
}
return ret;
}
static int rn5t618_adp_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct rn5t618_power_info *info = power_supply_get_drvdata(psy);
unsigned int chgstate;
bool online;
int ret;
ret = regmap_read(info->rn5t618->regmap, RN5T618_CHGSTATE, &chgstate);
if (ret)
return ret;
online = !!(chgstate & CHG_STATE_ADP_INPUT);
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
val->intval = online;
break;
case POWER_SUPPLY_PROP_STATUS:
if (!online) {
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
}
val->intval = rn5t618_decode_status(chgstate);
if (val->intval != POWER_SUPPLY_STATUS_CHARGING)
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
default:
return -EINVAL;
}
return 0;
}
static int rn5t618_usb_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct rn5t618_power_info *info = power_supply_get_drvdata(psy);
unsigned int chgstate;
bool online;
int ret;
ret = regmap_read(info->rn5t618->regmap, RN5T618_CHGSTATE, &chgstate);
if (ret)
return ret;
online = !!(chgstate & CHG_STATE_USB_INPUT);
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
val->intval = online;
break;
case POWER_SUPPLY_PROP_STATUS:
if (!online) {
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
}
val->intval = rn5t618_decode_status(chgstate);
if (val->intval != POWER_SUPPLY_STATUS_CHARGING)
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
default:
return -EINVAL;
}
return 0;
}
static const struct power_supply_desc rn5t618_battery_desc = {
.name = "rn5t618-battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = rn5t618_battery_props,
.num_properties = ARRAY_SIZE(rn5t618_battery_props),
.get_property = rn5t618_battery_get_property,
};
static const struct power_supply_desc rn5t618_adp_desc = {
.name = "rn5t618-adp",
.type = POWER_SUPPLY_TYPE_MAINS,
.properties = rn5t618_adp_props,
.num_properties = ARRAY_SIZE(rn5t618_adp_props),
.get_property = rn5t618_adp_get_property,
};
static const struct power_supply_desc rn5t618_usb_desc = {
.name = "rn5t618-usb",
.type = POWER_SUPPLY_TYPE_USB,
.properties = rn5t618_usb_props,
.num_properties = ARRAY_SIZE(rn5t618_usb_props),
.get_property = rn5t618_usb_get_property,
};
static irqreturn_t rn5t618_charger_irq(int irq, void *data)
{
struct device *dev = data;
struct rn5t618_power_info *info = dev_get_drvdata(dev);
unsigned int ctrl, stat1, stat2, err;
regmap_read(info->rn5t618->regmap, RN5T618_CHGERR_IRR, &err);
regmap_read(info->rn5t618->regmap, RN5T618_CHGCTRL_IRR, &ctrl);
regmap_read(info->rn5t618->regmap, RN5T618_CHGSTAT_IRR1, &stat1);
regmap_read(info->rn5t618->regmap, RN5T618_CHGSTAT_IRR2, &stat2);
regmap_write(info->rn5t618->regmap, RN5T618_CHGERR_IRR, 0);
regmap_write(info->rn5t618->regmap, RN5T618_CHGCTRL_IRR, 0);
regmap_write(info->rn5t618->regmap, RN5T618_CHGSTAT_IRR1, 0);
regmap_write(info->rn5t618->regmap, RN5T618_CHGSTAT_IRR2, 0);
dev_dbg(dev, "chgerr: %x chgctrl: %x chgstat: %x chgstat2: %x\n",
err, ctrl, stat1, stat2);
power_supply_changed(info->usb);
power_supply_changed(info->adp);
power_supply_changed(info->battery);
return IRQ_HANDLED;
}
static int rn5t618_power_probe(struct platform_device *pdev)
{
int ret = 0;
unsigned int v;
struct power_supply_config psy_cfg = {};
struct rn5t618_power_info *info;
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
info->pdev = pdev;
info->rn5t618 = dev_get_drvdata(pdev->dev.parent);
info->irq = -1;
platform_set_drvdata(pdev, info);
ret = regmap_read(info->rn5t618->regmap, RN5T618_CONTROL, &v);
if (ret)
return ret;
if (!(v & FG_ENABLE)) {
/* E.g. the vendor kernels of various Kobo and Tolino Ebook
* readers disable the fuel gauge on shutdown. If a kernel
* without fuel gauge support is booted after that, the fuel
* gauge will get decalibrated.
*/
dev_info(&pdev->dev, "Fuel gauge not enabled, enabling now\n");
dev_info(&pdev->dev, "Expect imprecise results\n");
regmap_update_bits(info->rn5t618->regmap, RN5T618_CONTROL,
FG_ENABLE, FG_ENABLE);
}
psy_cfg.drv_data = info;
info->battery = devm_power_supply_register(&pdev->dev,
&rn5t618_battery_desc,
&psy_cfg);
if (IS_ERR(info->battery)) {
ret = PTR_ERR(info->battery);
dev_err(&pdev->dev, "failed to register battery: %d\n", ret);
return ret;
}
info->adp = devm_power_supply_register(&pdev->dev,
&rn5t618_adp_desc,
&psy_cfg);
if (IS_ERR(info->adp)) {
ret = PTR_ERR(info->adp);
dev_err(&pdev->dev, "failed to register adp: %d\n", ret);
return ret;
}
info->usb = devm_power_supply_register(&pdev->dev,
&rn5t618_usb_desc,
&psy_cfg);
if (IS_ERR(info->usb)) {
ret = PTR_ERR(info->usb);
dev_err(&pdev->dev, "failed to register usb: %d\n", ret);
return ret;
}
if (info->rn5t618->irq_data)
info->irq = regmap_irq_get_virq(info->rn5t618->irq_data,
RN5T618_IRQ_CHG);
if (info->irq < 0)
info->irq = -1;
else {
ret = devm_request_threaded_irq(&pdev->dev, info->irq, NULL,
rn5t618_charger_irq,
IRQF_ONESHOT,
"rn5t618_power",
&pdev->dev);
if (ret < 0) {
dev_err(&pdev->dev, "request IRQ:%d fail\n",
info->irq);
info->irq = -1;
}
}
return 0;
}
static struct platform_driver rn5t618_power_driver = {
.driver = {
.name = "rn5t618-power",
},
.probe = rn5t618_power_probe,
};
module_platform_driver(rn5t618_power_driver);
MODULE_ALIAS("platform:rn5t618-power");
MODULE_DESCRIPTION("Power supply driver for RICOH RN5T618");
MODULE_LICENSE("GPL");
......@@ -1731,11 +1731,13 @@ static const struct of_device_id rt9455_of_match[] = {
};
MODULE_DEVICE_TABLE(of, rt9455_of_match);
#ifdef CONFIG_ACPI
static const struct acpi_device_id rt9455_i2c_acpi_match[] = {
{ "RT945500", 0 },
{ }
};
MODULE_DEVICE_TABLE(acpi, rt9455_i2c_acpi_match);
#endif
static struct i2c_driver rt9455_driver = {
.probe = rt9455_probe,
......
......@@ -193,7 +193,6 @@ struct sbs_info {
struct power_supply *power_supply;
bool is_present;
struct gpio_desc *gpio_detect;
bool enable_detection;
bool charger_broadcasts;
int last_state;
int poll_time;
......@@ -480,37 +479,6 @@ static bool sbs_bat_needs_calibration(struct i2c_client *client)
return !!(ret & BIT(7));
}
static int sbs_get_battery_presence_and_health(
struct i2c_client *client, enum power_supply_property psp,
union power_supply_propval *val)
{
int ret;
/* Dummy command; if it succeeds, battery is present. */
ret = sbs_read_word_data(client, sbs_data[REG_STATUS].addr);
if (ret < 0) { /* battery not present*/
if (psp == POWER_SUPPLY_PROP_PRESENT) {
val->intval = 0;
return 0;
}
return ret;
}
if (psp == POWER_SUPPLY_PROP_PRESENT)
val->intval = 1; /* battery present */
else { /* POWER_SUPPLY_PROP_HEALTH */
if (sbs_bat_needs_calibration(client)) {
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;
}
static int sbs_get_ti_battery_presence_and_health(
struct i2c_client *client, enum power_supply_property psp,
union power_supply_propval *val)
......@@ -569,6 +537,41 @@ static int sbs_get_ti_battery_presence_and_health(
return 0;
}
static int sbs_get_battery_presence_and_health(
struct i2c_client *client, enum power_supply_property psp,
union power_supply_propval *val)
{
struct sbs_info *chip = i2c_get_clientdata(client);
int ret;
if (chip->flags & SBS_FLAGS_TI_BQ20ZX5)
return sbs_get_ti_battery_presence_and_health(client, psp, val);
/* Dummy command; if it succeeds, battery is present. */
ret = sbs_read_word_data(client, sbs_data[REG_STATUS].addr);
if (ret < 0) { /* battery not present*/
if (psp == POWER_SUPPLY_PROP_PRESENT) {
val->intval = 0;
return 0;
}
return ret;
}
if (psp == POWER_SUPPLY_PROP_PRESENT)
val->intval = 1; /* battery present */
else { /* POWER_SUPPLY_PROP_HEALTH */
if (sbs_bat_needs_calibration(client)) {
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;
}
static int sbs_get_battery_property(struct i2c_client *client,
int reg_offset, enum power_supply_property psp,
union power_supply_propval *val)
......@@ -871,12 +874,7 @@ static int sbs_get_property(struct power_supply *psy,
switch (psp) {
case POWER_SUPPLY_PROP_PRESENT:
case POWER_SUPPLY_PROP_HEALTH:
if (chip->flags & SBS_FLAGS_TI_BQ20ZX5)
ret = sbs_get_ti_battery_presence_and_health(client,
psp, val);
else
ret = sbs_get_battery_presence_and_health(client, psp,
val);
ret = sbs_get_battery_presence_and_health(client, psp, val);
/* this can only be true if no gpio is used */
if (psp == POWER_SUPPLY_PROP_PRESENT)
......@@ -967,12 +965,15 @@ static int sbs_get_property(struct power_supply *psy,
return -EINVAL;
}
if (!chip->enable_detection)
goto done;
if (!chip->gpio_detect && chip->is_present != (ret >= 0)) {
bool old_present = chip->is_present;
union power_supply_propval val;
int err = sbs_get_battery_presence_and_health(
client, POWER_SUPPLY_PROP_PRESENT, &val);
sbs_update_presence(chip, !err && val.intval);
if (!chip->gpio_detect &&
chip->is_present != (ret >= 0)) {
sbs_update_presence(chip, (ret >= 0));
if (old_present != chip->is_present)
power_supply_changed(chip->power_supply);
}
......@@ -980,19 +981,14 @@ static int sbs_get_property(struct power_supply *psy,
if (!ret) {
/* Convert units to match requirements for power supply class */
sbs_unit_adjustment(client, psp, val);
}
dev_dbg(&client->dev,
"%s: property = %d, value = %x\n", __func__, psp, val->intval);
if (ret && chip->is_present)
return ret;
"%s: property = %d, value = %x\n", __func__,
psp, val->intval);
} else if (!chip->is_present) {
/* battery not present, so return NODATA for properties */
if (ret)
return -ENODATA;
return 0;
ret = -ENODATA;
}
return ret;
}
static void sbs_supply_changed(struct sbs_info *chip)
......@@ -1098,7 +1094,6 @@ static int sbs_probe(struct i2c_client *client)
chip->flags = (u32)(uintptr_t)device_get_match_data(&client->dev);
chip->client = client;
chip->enable_detection = false;
psy_cfg.of_node = client->dev.of_node;
psy_cfg.drv_data = chip;
chip->last_state = POWER_SUPPLY_STATUS_UNKNOWN;
......@@ -1159,15 +1154,19 @@ static int sbs_probe(struct i2c_client *client)
* to the battery.
*/
if (!(force_load || chip->gpio_detect)) {
rc = sbs_read_word_data(client, sbs_data[REG_STATUS].addr);
union power_supply_propval val;
if (rc < 0) {
dev_err(&client->dev, "%s: Failed to get device status\n",
__func__);
rc = sbs_get_battery_presence_and_health(
client, POWER_SUPPLY_PROP_PRESENT, &val);
if (rc < 0 || !val.intval) {
dev_err(&client->dev, "Failed to get present status\n");
rc = -ENODEV;
goto exit_psupply;
}
}
INIT_DELAYED_WORK(&chip->work, sbs_delayed_work);
chip->power_supply = devm_power_supply_register(&client->dev, sbs_desc,
&psy_cfg);
if (IS_ERR(chip->power_supply)) {
......@@ -1180,10 +1179,6 @@ static int sbs_probe(struct i2c_client *client)
dev_info(&client->dev,
"%s: battery gas gauge device registered\n", client->name);
INIT_DELAYED_WORK(&chip->work, sbs_delayed_work);
chip->enable_detection = true;
return 0;
exit_psupply:
......
......@@ -16,11 +16,18 @@
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/power_supply.h>
#include <linux/power/smb347-charger.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <dt-bindings/power/summit,smb347-charger.h>
/* Use the default compensation method */
#define SMB3XX_SOFT_TEMP_COMPENSATE_DEFAULT -1
/* Use default factory programmed value for hard/soft temperature limit */
#define SMB3XX_TEMP_USE_DEFAULT -273
/*
* Configuration registers. These are mirrored to volatile RAM and can be
* written once %CMD_A_ALLOW_WRITE is set in %CMD_A register. They will be
......@@ -122,82 +129,140 @@
/**
* struct smb347_charger - smb347 charger instance
* @lock: protects concurrent access to online variables
* @dev: pointer to device
* @regmap: pointer to driver regmap
* @mains: power_supply instance for AC/DC power
* @usb: power_supply instance for USB power
* @battery: power_supply instance for battery
* @id: SMB charger ID
* @mains_online: is AC/DC input connected
* @usb_online: is USB input connected
* @charging_enabled: is charging enabled
* @pdata: pointer to platform data
* @max_charge_current: maximum current (in uA) the battery can be charged
* @max_charge_voltage: maximum voltage (in uV) the battery can be charged
* @pre_charge_current: current (in uA) to use in pre-charging phase
* @termination_current: current (in uA) used to determine when the
* charging cycle terminates
* @pre_to_fast_voltage: voltage (in uV) treshold used for transitioning to
* pre-charge to fast charge mode
* @mains_current_limit: maximum input current drawn from AC/DC input (in uA)
* @usb_hc_current_limit: maximum input high current (in uA) drawn from USB
* input
* @chip_temp_threshold: die temperature where device starts limiting charge
* current [%100 - %130] (in degree C)
* @soft_cold_temp_limit: soft cold temperature limit [%0 - %15] (in degree C),
* granularity is 5 deg C.
* @soft_hot_temp_limit: soft hot temperature limit [%40 - %55] (in degree C),
* granularity is 5 deg C.
* @hard_cold_temp_limit: hard cold temperature limit [%-5 - %10] (in degree C),
* granularity is 5 deg C.
* @hard_hot_temp_limit: hard hot temperature limit [%50 - %65] (in degree C),
* granularity is 5 deg C.
* @suspend_on_hard_temp_limit: suspend charging when hard limit is hit
* @soft_temp_limit_compensation: compensation method when soft temperature
* limit is hit
* @charge_current_compensation: current (in uA) for charging compensation
* current when temperature hits soft limits
* @use_mains: AC/DC input can be used
* @use_usb: USB input can be used
* @use_usb_otg: USB OTG output can be used (not implemented yet)
* @enable_control: how charging enable/disable is controlled
* (driver/pin controls)
*
* @use_main, @use_usb, and @use_usb_otg are means to enable/disable
* hardware support for these. This is useful when we want to have for
* example OTG charging controlled via OTG transceiver driver and not by
* the SMB347 hardware.
*
* Hard and soft temperature limit values are given as described in the
* device data sheet and assuming NTC beta value is %3750. Even if this is
* not the case, these values should be used. They can be mapped to the
* corresponding NTC beta values with the help of table %2 in the data
* sheet. So for example if NTC beta is %3375 and we want to program hard
* hot limit to be %53 deg C, @hard_hot_temp_limit should be set to %50.
*
* If zero value is given in any of the current and voltage values, the
* factory programmed default will be used. For soft/hard temperature
* values, pass in %SMB3XX_TEMP_USE_DEFAULT instead.
*/
struct smb347_charger {
struct mutex lock;
struct device *dev;
struct regmap *regmap;
struct power_supply *mains;
struct power_supply *usb;
struct power_supply *battery;
unsigned int id;
bool mains_online;
bool usb_online;
bool charging_enabled;
const struct smb347_charger_platform_data *pdata;
unsigned int max_charge_current;
unsigned int max_charge_voltage;
unsigned int pre_charge_current;
unsigned int termination_current;
unsigned int pre_to_fast_voltage;
unsigned int mains_current_limit;
unsigned int usb_hc_current_limit;
unsigned int chip_temp_threshold;
int soft_cold_temp_limit;
int soft_hot_temp_limit;
int hard_cold_temp_limit;
int hard_hot_temp_limit;
bool suspend_on_hard_temp_limit;
unsigned int soft_temp_limit_compensation;
unsigned int charge_current_compensation;
bool use_mains;
bool use_usb;
bool use_usb_otg;
unsigned int enable_control;
};
/* Fast charge current in uA */
static const unsigned int fcc_tbl[] = {
700000,
900000,
1200000,
1500000,
1800000,
2000000,
2200000,
2500000,
enum smb_charger_chipid {
SMB345,
SMB347,
SMB358,
NUM_CHIP_TYPES,
};
/* Fast charge current in uA */
static const unsigned int fcc_tbl[NUM_CHIP_TYPES][8] = {
[SMB345] = { 200000, 450000, 600000, 900000,
1300000, 1500000, 1800000, 2000000 },
[SMB347] = { 700000, 900000, 1200000, 1500000,
1800000, 2000000, 2200000, 2500000 },
[SMB358] = { 200000, 450000, 600000, 900000,
1300000, 1500000, 1800000, 2000000 },
};
/* Pre-charge current in uA */
static const unsigned int pcc_tbl[] = {
100000,
150000,
200000,
250000,
static const unsigned int pcc_tbl[NUM_CHIP_TYPES][4] = {
[SMB345] = { 150000, 250000, 350000, 450000 },
[SMB347] = { 100000, 150000, 200000, 250000 },
[SMB358] = { 150000, 250000, 350000, 450000 },
};
/* Termination current in uA */
static const unsigned int tc_tbl[] = {
37500,
50000,
100000,
150000,
200000,
250000,
500000,
600000,
static const unsigned int tc_tbl[NUM_CHIP_TYPES][8] = {
[SMB345] = { 30000, 40000, 60000, 80000,
100000, 125000, 150000, 200000 },
[SMB347] = { 37500, 50000, 100000, 150000,
200000, 250000, 500000, 600000 },
[SMB358] = { 30000, 40000, 60000, 80000,
100000, 125000, 150000, 200000 },
};
/* Input current limit in uA */
static const unsigned int icl_tbl[] = {
300000,
500000,
700000,
900000,
1200000,
1500000,
1800000,
2000000,
2200000,
2500000,
static const unsigned int icl_tbl[NUM_CHIP_TYPES][10] = {
[SMB345] = { 300000, 500000, 700000, 1000000, 1500000,
1800000, 2000000, 2000000, 2000000, 2000000 },
[SMB347] = { 300000, 500000, 700000, 900000, 1200000,
1500000, 1800000, 2000000, 2200000, 2500000 },
[SMB358] = { 300000, 500000, 700000, 1000000, 1500000,
1800000, 2000000, 2000000, 2000000, 2000000 },
};
/* Charge current compensation in uA */
static const unsigned int ccc_tbl[] = {
250000,
700000,
900000,
1200000,
static const unsigned int ccc_tbl[NUM_CHIP_TYPES][4] = {
[SMB345] = { 200000, 450000, 600000, 900000 },
[SMB347] = { 250000, 700000, 900000, 1200000 },
[SMB358] = { 200000, 450000, 600000, 900000 },
};
/* Convert register value to current using lookup table */
......@@ -242,16 +307,14 @@ static int smb347_update_ps_status(struct smb347_charger *smb)
* Dc and usb are set depending on whether they are enabled in
* platform data _and_ whether corresponding undervoltage is set.
*/
if (smb->pdata->use_mains)
if (smb->use_mains)
dc = !(val & IRQSTAT_E_DCIN_UV_STAT);
if (smb->pdata->use_usb)
if (smb->use_usb)
usb = !(val & IRQSTAT_E_USBIN_UV_STAT);
mutex_lock(&smb->lock);
ret = smb->mains_online != dc || smb->usb_online != usb;
smb->mains_online = dc;
smb->usb_online = usb;
mutex_unlock(&smb->lock);
return ret;
}
......@@ -267,13 +330,7 @@ static int smb347_update_ps_status(struct smb347_charger *smb)
*/
static bool smb347_is_ps_online(struct smb347_charger *smb)
{
bool ret;
mutex_lock(&smb->lock);
ret = smb->usb_online || smb->mains_online;
mutex_unlock(&smb->lock);
return ret;
return smb->usb_online || smb->mains_online;
}
/**
......@@ -302,19 +359,18 @@ static int smb347_charging_set(struct smb347_charger *smb, bool enable)
{
int ret = 0;
if (smb->pdata->enable_control != SMB347_CHG_ENABLE_SW) {
if (smb->enable_control != SMB3XX_CHG_ENABLE_SW) {
dev_dbg(smb->dev, "charging enable/disable in SW disabled\n");
return 0;
}
mutex_lock(&smb->lock);
if (smb->charging_enabled != enable) {
ret = regmap_update_bits(smb->regmap, CMD_A, CMD_A_CHG_ENABLED,
enable ? CMD_A_CHG_ENABLED : 0);
if (!ret)
smb->charging_enabled = enable;
}
mutex_unlock(&smb->lock);
return ret;
}
......@@ -352,11 +408,12 @@ static int smb347_start_stop_charging(struct smb347_charger *smb)
static int smb347_set_charge_current(struct smb347_charger *smb)
{
unsigned int id = smb->id;
int ret;
if (smb->pdata->max_charge_current) {
ret = current_to_hw(fcc_tbl, ARRAY_SIZE(fcc_tbl),
smb->pdata->max_charge_current);
if (smb->max_charge_current) {
ret = current_to_hw(fcc_tbl[id], ARRAY_SIZE(fcc_tbl[id]),
smb->max_charge_current);
if (ret < 0)
return ret;
......@@ -367,9 +424,9 @@ static int smb347_set_charge_current(struct smb347_charger *smb)
return ret;
}
if (smb->pdata->pre_charge_current) {
ret = current_to_hw(pcc_tbl, ARRAY_SIZE(pcc_tbl),
smb->pdata->pre_charge_current);
if (smb->pre_charge_current) {
ret = current_to_hw(pcc_tbl[id], ARRAY_SIZE(pcc_tbl[id]),
smb->pre_charge_current);
if (ret < 0)
return ret;
......@@ -380,9 +437,9 @@ static int smb347_set_charge_current(struct smb347_charger *smb)
return ret;
}
if (smb->pdata->termination_current) {
ret = current_to_hw(tc_tbl, ARRAY_SIZE(tc_tbl),
smb->pdata->termination_current);
if (smb->termination_current) {
ret = current_to_hw(tc_tbl[id], ARRAY_SIZE(tc_tbl[id]),
smb->termination_current);
if (ret < 0)
return ret;
......@@ -397,11 +454,12 @@ static int smb347_set_charge_current(struct smb347_charger *smb)
static int smb347_set_current_limits(struct smb347_charger *smb)
{
unsigned int id = smb->id;
int ret;
if (smb->pdata->mains_current_limit) {
ret = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl),
smb->pdata->mains_current_limit);
if (smb->mains_current_limit) {
ret = current_to_hw(icl_tbl[id], ARRAY_SIZE(icl_tbl[id]),
smb->mains_current_limit);
if (ret < 0)
return ret;
......@@ -412,9 +470,9 @@ static int smb347_set_current_limits(struct smb347_charger *smb)
return ret;
}
if (smb->pdata->usb_hc_current_limit) {
ret = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl),
smb->pdata->usb_hc_current_limit);
if (smb->usb_hc_current_limit) {
ret = current_to_hw(icl_tbl[id], ARRAY_SIZE(icl_tbl[id]),
smb->usb_hc_current_limit);
if (ret < 0)
return ret;
......@@ -431,8 +489,8 @@ static int smb347_set_voltage_limits(struct smb347_charger *smb)
{
int ret;
if (smb->pdata->pre_to_fast_voltage) {
ret = smb->pdata->pre_to_fast_voltage;
if (smb->pre_to_fast_voltage) {
ret = smb->pre_to_fast_voltage;
/* uV */
ret = clamp_val(ret, 2400000, 3000000) - 2400000;
......@@ -445,8 +503,8 @@ static int smb347_set_voltage_limits(struct smb347_charger *smb)
return ret;
}
if (smb->pdata->max_charge_voltage) {
ret = smb->pdata->max_charge_voltage;
if (smb->max_charge_voltage) {
ret = smb->max_charge_voltage;
/* uV */
ret = clamp_val(ret, 3500000, 4500000) - 3500000;
......@@ -463,12 +521,13 @@ static int smb347_set_voltage_limits(struct smb347_charger *smb)
static int smb347_set_temp_limits(struct smb347_charger *smb)
{
unsigned int id = smb->id;
bool enable_therm_monitor = false;
int ret = 0;
int val;
if (smb->pdata->chip_temp_threshold) {
val = smb->pdata->chip_temp_threshold;
if (smb->chip_temp_threshold) {
val = smb->chip_temp_threshold;
/* degree C */
val = clamp_val(val, 100, 130) - 100;
......@@ -481,8 +540,8 @@ static int smb347_set_temp_limits(struct smb347_charger *smb)
return ret;
}
if (smb->pdata->soft_cold_temp_limit != SMB347_TEMP_USE_DEFAULT) {
val = smb->pdata->soft_cold_temp_limit;
if (smb->soft_cold_temp_limit != SMB3XX_TEMP_USE_DEFAULT) {
val = smb->soft_cold_temp_limit;
val = clamp_val(val, 0, 15);
val /= 5;
......@@ -498,8 +557,8 @@ static int smb347_set_temp_limits(struct smb347_charger *smb)
enable_therm_monitor = true;
}
if (smb->pdata->soft_hot_temp_limit != SMB347_TEMP_USE_DEFAULT) {
val = smb->pdata->soft_hot_temp_limit;
if (smb->soft_hot_temp_limit != SMB3XX_TEMP_USE_DEFAULT) {
val = smb->soft_hot_temp_limit;
val = clamp_val(val, 40, 55) - 40;
val /= 5;
......@@ -513,8 +572,8 @@ static int smb347_set_temp_limits(struct smb347_charger *smb)
enable_therm_monitor = true;
}
if (smb->pdata->hard_cold_temp_limit != SMB347_TEMP_USE_DEFAULT) {
val = smb->pdata->hard_cold_temp_limit;
if (smb->hard_cold_temp_limit != SMB3XX_TEMP_USE_DEFAULT) {
val = smb->hard_cold_temp_limit;
val = clamp_val(val, -5, 10) + 5;
val /= 5;
......@@ -530,8 +589,8 @@ static int smb347_set_temp_limits(struct smb347_charger *smb)
enable_therm_monitor = true;
}
if (smb->pdata->hard_hot_temp_limit != SMB347_TEMP_USE_DEFAULT) {
val = smb->pdata->hard_hot_temp_limit;
if (smb->hard_hot_temp_limit != SMB3XX_TEMP_USE_DEFAULT) {
val = smb->hard_hot_temp_limit;
val = clamp_val(val, 50, 65) - 50;
val /= 5;
......@@ -562,16 +621,16 @@ static int smb347_set_temp_limits(struct smb347_charger *smb)
return ret;
}
if (smb->pdata->suspend_on_hard_temp_limit) {
if (smb->suspend_on_hard_temp_limit) {
ret = regmap_update_bits(smb->regmap, CFG_SYSOK,
CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED, 0);
if (ret < 0)
return ret;
}
if (smb->pdata->soft_temp_limit_compensation !=
SMB347_SOFT_TEMP_COMPENSATE_DEFAULT) {
val = smb->pdata->soft_temp_limit_compensation & 0x3;
if (smb->soft_temp_limit_compensation !=
SMB3XX_SOFT_TEMP_COMPENSATE_DEFAULT) {
val = smb->soft_temp_limit_compensation & 0x3;
ret = regmap_update_bits(smb->regmap, CFG_THERM,
CFG_THERM_SOFT_HOT_COMPENSATION_MASK,
......@@ -586,9 +645,9 @@ static int smb347_set_temp_limits(struct smb347_charger *smb)
return ret;
}
if (smb->pdata->charge_current_compensation) {
val = current_to_hw(ccc_tbl, ARRAY_SIZE(ccc_tbl),
smb->pdata->charge_current_compensation);
if (smb->charge_current_compensation) {
val = current_to_hw(ccc_tbl[id], ARRAY_SIZE(ccc_tbl[id]),
smb->charge_current_compensation);
if (val < 0)
return val;
......@@ -647,7 +706,7 @@ static int smb347_hw_init(struct smb347_charger *smb)
goto fail;
/* If USB charging is disabled we put the USB in suspend mode */
if (!smb->pdata->use_usb) {
if (!smb->use_usb) {
ret = regmap_update_bits(smb->regmap, CMD_A,
CMD_A_SUSPEND_ENABLED,
CMD_A_SUSPEND_ENABLED);
......@@ -660,7 +719,7 @@ static int smb347_hw_init(struct smb347_charger *smb)
* support for driving VBUS. Otherwise we disable it.
*/
ret = regmap_update_bits(smb->regmap, CFG_OTHER, CFG_OTHER_RID_MASK,
smb->pdata->use_usb_otg ? CFG_OTHER_RID_ENABLED_AUTO_OTG : 0);
smb->use_usb_otg ? CFG_OTHER_RID_ENABLED_AUTO_OTG : 0);
if (ret < 0)
goto fail;
......@@ -669,11 +728,11 @@ static int smb347_hw_init(struct smb347_charger *smb)
* command register unless pin control is specified in the platform
* data.
*/
switch (smb->pdata->enable_control) {
case SMB347_CHG_ENABLE_PIN_ACTIVE_LOW:
switch (smb->enable_control) {
case SMB3XX_CHG_ENABLE_PIN_ACTIVE_LOW:
val = CFG_PIN_EN_CTRL_ACTIVE_LOW;
break;
case SMB347_CHG_ENABLE_PIN_ACTIVE_HIGH:
case SMB3XX_CHG_ENABLE_PIN_ACTIVE_HIGH:
val = CFG_PIN_EN_CTRL_ACTIVE_HIGH;
break;
default:
......@@ -742,7 +801,10 @@ static irqreturn_t smb347_interrupt(int irq, void *data)
*/
if (stat_c & STAT_C_CHARGER_ERROR) {
dev_err(smb->dev, "charging stopped due to charger error\n");
power_supply_changed(smb->battery);
if (smb->use_mains)
power_supply_changed(smb->mains);
if (smb->use_usb)
power_supply_changed(smb->usb);
handled = true;
}
......@@ -752,8 +814,12 @@ static irqreturn_t smb347_interrupt(int irq, void *data)
* disabled by the hardware.
*/
if (irqstat_c & (IRQSTAT_C_TERMINATION_IRQ | IRQSTAT_C_TAPER_IRQ)) {
if (irqstat_c & IRQSTAT_C_TERMINATION_STAT)
power_supply_changed(smb->battery);
if (irqstat_c & IRQSTAT_C_TERMINATION_STAT) {
if (smb->use_mains)
power_supply_changed(smb->mains);
if (smb->use_usb)
power_supply_changed(smb->usb);
}
dev_dbg(smb->dev, "going to HW maintenance mode\n");
handled = true;
}
......@@ -767,7 +833,10 @@ static irqreturn_t smb347_interrupt(int irq, void *data)
if (irqstat_d & IRQSTAT_D_CHARGE_TIMEOUT_STAT)
dev_warn(smb->dev, "charging stopped due to timeout\n");
power_supply_changed(smb->battery);
if (smb->use_mains)
power_supply_changed(smb->mains);
if (smb->use_usb)
power_supply_changed(smb->usb);
handled = true;
}
......@@ -778,9 +847,9 @@ static irqreturn_t smb347_interrupt(int irq, void *data)
if (irqstat_e & (IRQSTAT_E_USBIN_UV_IRQ | IRQSTAT_E_DCIN_UV_IRQ)) {
if (smb347_update_ps_status(smb) > 0) {
smb347_start_stop_charging(smb);
if (smb->pdata->use_mains)
if (smb->use_mains)
power_supply_changed(smb->mains);
if (smb->pdata->use_usb)
if (smb->use_usb)
power_supply_changed(smb->usb);
}
handled = true;
......@@ -835,22 +904,17 @@ static inline int smb347_irq_disable(struct smb347_charger *smb)
static int smb347_irq_init(struct smb347_charger *smb,
struct i2c_client *client)
{
const struct smb347_charger_platform_data *pdata = smb->pdata;
int ret, irq = gpio_to_irq(pdata->irq_gpio);
ret = gpio_request_one(pdata->irq_gpio, GPIOF_IN, client->name);
if (ret < 0)
goto fail;
int ret;
ret = request_threaded_irq(irq, NULL, smb347_interrupt,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
ret = devm_request_threaded_irq(smb->dev, client->irq, NULL,
smb347_interrupt, IRQF_ONESHOT,
client->name, smb);
if (ret < 0)
goto fail_gpio;
return ret;
ret = smb347_set_writable(smb, true);
if (ret < 0)
goto fail_irq;
return ret;
/*
* Configure the STAT output to be suitable for interrupts: disable
......@@ -860,20 +924,10 @@ static int smb347_irq_init(struct smb347_charger *smb,
CFG_STAT_ACTIVE_HIGH | CFG_STAT_DISABLED,
CFG_STAT_DISABLED);
if (ret < 0)
goto fail_readonly;
client->irq = 0;
smb347_set_writable(smb, false);
client->irq = irq;
return 0;
fail_readonly:
smb347_set_writable(smb, false);
fail_irq:
free_irq(irq, smb);
fail_gpio:
gpio_free(pdata->irq_gpio);
fail:
client->irq = 0;
return ret;
}
......@@ -883,6 +937,7 @@ static int smb347_irq_init(struct smb347_charger *smb,
*/
static int get_const_charge_current(struct smb347_charger *smb)
{
unsigned int id = smb->id;
int ret, intval;
unsigned int v;
......@@ -898,10 +953,12 @@ static int get_const_charge_current(struct smb347_charger *smb)
* and we can detect which table to use from bit 5.
*/
if (v & 0x20) {
intval = hw_to_current(fcc_tbl, ARRAY_SIZE(fcc_tbl), v & 7);
intval = hw_to_current(fcc_tbl[id],
ARRAY_SIZE(fcc_tbl[id]), v & 7);
} else {
v >>= 3;
intval = hw_to_current(pcc_tbl, ARRAY_SIZE(pcc_tbl), v & 7);
intval = hw_to_current(pcc_tbl[id],
ARRAY_SIZE(pcc_tbl[id]), v & 7);
}
return intval;
......@@ -932,95 +989,19 @@ static int get_const_charge_voltage(struct smb347_charger *smb)
return intval;
}
static int smb347_mains_get_property(struct power_supply *psy,
enum power_supply_property prop,
union power_supply_propval *val)
{
struct smb347_charger *smb = power_supply_get_drvdata(psy);
int ret;
switch (prop) {
case POWER_SUPPLY_PROP_ONLINE:
val->intval = smb->mains_online;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
ret = get_const_charge_voltage(smb);
if (ret < 0)
return ret;
else
val->intval = ret;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
ret = get_const_charge_current(smb);
if (ret < 0)
return ret;
else
val->intval = ret;
break;
default:
return -EINVAL;
}
return 0;
}
static enum power_supply_property smb347_mains_properties[] = {
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
};
static int smb347_usb_get_property(struct power_supply *psy,
enum power_supply_property prop,
union power_supply_propval *val)
{
struct smb347_charger *smb = power_supply_get_drvdata(psy);
int ret;
switch (prop) {
case POWER_SUPPLY_PROP_ONLINE:
val->intval = smb->usb_online;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
ret = get_const_charge_voltage(smb);
if (ret < 0)
return ret;
else
val->intval = ret;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
ret = get_const_charge_current(smb);
if (ret < 0)
return ret;
else
val->intval = ret;
break;
default:
return -EINVAL;
}
return 0;
}
static enum power_supply_property smb347_usb_properties[] = {
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
};
static int smb347_get_charging_status(struct smb347_charger *smb)
static int smb347_get_charging_status(struct smb347_charger *smb,
struct power_supply *psy)
{
int ret, status;
unsigned int val;
if (!smb347_is_ps_online(smb))
if (psy->desc->type == POWER_SUPPLY_TYPE_USB) {
if (!smb->usb_online)
return POWER_SUPPLY_STATUS_DISCHARGING;
} else {
if (!smb->mains_online)
return POWER_SUPPLY_STATUS_DISCHARGING;
}
ret = regmap_read(smb->regmap, STAT_C, &val);
if (ret < 0)
......@@ -1059,29 +1040,29 @@ static int smb347_get_charging_status(struct smb347_charger *smb)
return status;
}
static int smb347_battery_get_property(struct power_supply *psy,
static int smb347_get_property_locked(struct power_supply *psy,
enum power_supply_property prop,
union power_supply_propval *val)
{
struct smb347_charger *smb = power_supply_get_drvdata(psy);
const struct smb347_charger_platform_data *pdata = smb->pdata;
int ret;
ret = smb347_update_ps_status(smb);
if (ret < 0)
return ret;
switch (prop) {
case POWER_SUPPLY_PROP_STATUS:
ret = smb347_get_charging_status(smb);
ret = smb347_get_charging_status(smb, psy);
if (ret < 0)
return ret;
val->intval = ret;
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
if (!smb347_is_ps_online(smb))
if (psy->desc->type == POWER_SUPPLY_TYPE_USB) {
if (!smb->usb_online)
return -ENODATA;
} else {
if (!smb->mains_online)
return -ENODATA;
}
/*
* We handle trickle and pre-charging the same, and taper
......@@ -1100,24 +1081,25 @@ static int smb347_battery_get_property(struct power_supply *psy,
}
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = pdata->battery_info.technology;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
val->intval = pdata->battery_info.voltage_min_design;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
val->intval = pdata->battery_info.voltage_max_design;
case POWER_SUPPLY_PROP_ONLINE:
if (psy->desc->type == POWER_SUPPLY_TYPE_USB)
val->intval = smb->usb_online;
else
val->intval = smb->mains_online;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
val->intval = pdata->battery_info.charge_full_design;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
ret = get_const_charge_voltage(smb);
if (ret < 0)
return ret;
val->intval = ret;
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = pdata->battery_info.name;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
ret = get_const_charge_current(smb);
if (ret < 0)
return ret;
val->intval = ret;
break;
default:
......@@ -1127,14 +1109,27 @@ static int smb347_battery_get_property(struct power_supply *psy,
return 0;
}
static enum power_supply_property smb347_battery_properties[] = {
static int smb347_get_property(struct power_supply *psy,
enum power_supply_property prop,
union power_supply_propval *val)
{
struct smb347_charger *smb = power_supply_get_drvdata(psy);
struct i2c_client *client = to_i2c_client(smb->dev);
int ret;
disable_irq(client->irq);
ret = smb347_get_property_locked(psy, prop, val);
enable_irq(client->irq);
return ret;
}
static enum power_supply_property smb347_properties[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
};
static bool smb347_volatile_reg(struct device *dev, unsigned int reg)
......@@ -1180,6 +1175,96 @@ static bool smb347_readable_reg(struct device *dev, unsigned int reg)
return smb347_volatile_reg(dev, reg);
}
static void smb347_dt_parse_dev_info(struct smb347_charger *smb)
{
struct device *dev = smb->dev;
smb->soft_temp_limit_compensation =
SMB3XX_SOFT_TEMP_COMPENSATE_DEFAULT;
/*
* These properties come from the battery info, still we need to
* pre-initialize the values. See smb347_get_battery_info() below.
*/
smb->soft_cold_temp_limit = SMB3XX_TEMP_USE_DEFAULT;
smb->hard_cold_temp_limit = SMB3XX_TEMP_USE_DEFAULT;
smb->soft_hot_temp_limit = SMB3XX_TEMP_USE_DEFAULT;
smb->hard_hot_temp_limit = SMB3XX_TEMP_USE_DEFAULT;
/* Charging constraints */
device_property_read_u32(dev, "summit,fast-voltage-threshold-microvolt",
&smb->pre_to_fast_voltage);
device_property_read_u32(dev, "summit,mains-current-limit-microamp",
&smb->mains_current_limit);
device_property_read_u32(dev, "summit,usb-current-limit-microamp",
&smb->usb_hc_current_limit);
/* For thermometer monitoring */
device_property_read_u32(dev, "summit,chip-temperature-threshold-celsius",
&smb->chip_temp_threshold);
device_property_read_u32(dev, "summit,soft-compensation-method",
&smb->soft_temp_limit_compensation);
device_property_read_u32(dev, "summit,charge-current-compensation-microamp",
&smb->charge_current_compensation);
/* Supported charging mode */
smb->use_mains = device_property_read_bool(dev, "summit,enable-mains-charging");
smb->use_usb = device_property_read_bool(dev, "summit,enable-usb-charging");
smb->use_usb_otg = device_property_read_bool(dev, "summit,enable-otg-charging");
/* Select charging control */
device_property_read_u32(dev, "summit,enable-charge-control",
&smb->enable_control);
}
static int smb347_get_battery_info(struct smb347_charger *smb)
{
struct power_supply_battery_info info = {};
struct power_supply *supply;
int err;
if (smb->mains)
supply = smb->mains;
else
supply = smb->usb;
err = power_supply_get_battery_info(supply, &info);
if (err == -ENXIO || err == -ENODEV)
return 0;
if (err)
return err;
if (info.constant_charge_current_max_ua != -EINVAL)
smb->max_charge_current = info.constant_charge_current_max_ua;
if (info.constant_charge_voltage_max_uv != -EINVAL)
smb->max_charge_voltage = info.constant_charge_voltage_max_uv;
if (info.precharge_current_ua != -EINVAL)
smb->pre_charge_current = info.precharge_current_ua;
if (info.charge_term_current_ua != -EINVAL)
smb->termination_current = info.charge_term_current_ua;
if (info.temp_alert_min != INT_MIN)
smb->soft_cold_temp_limit = info.temp_alert_min;
if (info.temp_alert_max != INT_MAX)
smb->soft_hot_temp_limit = info.temp_alert_max;
if (info.temp_min != INT_MIN)
smb->hard_cold_temp_limit = info.temp_min;
if (info.temp_max != INT_MAX)
smb->hard_hot_temp_limit = info.temp_max;
/* Suspend when battery temperature is outside hard limits */
if (smb->hard_cold_temp_limit != SMB3XX_TEMP_USE_DEFAULT ||
smb->hard_hot_temp_limit != SMB3XX_TEMP_USE_DEFAULT)
smb->suspend_on_hard_temp_limit = true;
return 0;
}
static const struct regmap_config smb347_regmap = {
.reg_bits = 8,
.val_bits = 8,
......@@ -1191,98 +1276,71 @@ static const struct regmap_config smb347_regmap = {
static const struct power_supply_desc smb347_mains_desc = {
.name = "smb347-mains",
.type = POWER_SUPPLY_TYPE_MAINS,
.get_property = smb347_mains_get_property,
.properties = smb347_mains_properties,
.num_properties = ARRAY_SIZE(smb347_mains_properties),
.get_property = smb347_get_property,
.properties = smb347_properties,
.num_properties = ARRAY_SIZE(smb347_properties),
};
static const struct power_supply_desc smb347_usb_desc = {
.name = "smb347-usb",
.type = POWER_SUPPLY_TYPE_USB,
.get_property = smb347_usb_get_property,
.properties = smb347_usb_properties,
.num_properties = ARRAY_SIZE(smb347_usb_properties),
};
static const struct power_supply_desc smb347_battery_desc = {
.name = "smb347-battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.get_property = smb347_battery_get_property,
.properties = smb347_battery_properties,
.num_properties = ARRAY_SIZE(smb347_battery_properties),
.get_property = smb347_get_property,
.properties = smb347_properties,
.num_properties = ARRAY_SIZE(smb347_properties),
};
static int smb347_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
static char *battery[] = { "smb347-battery" };
const struct smb347_charger_platform_data *pdata;
struct power_supply_config mains_usb_cfg = {}, battery_cfg = {};
struct power_supply_config mains_usb_cfg = {};
struct device *dev = &client->dev;
struct smb347_charger *smb;
int ret;
pdata = dev->platform_data;
if (!pdata)
return -EINVAL;
if (!pdata->use_mains && !pdata->use_usb)
return -EINVAL;
smb = devm_kzalloc(dev, sizeof(*smb), GFP_KERNEL);
if (!smb)
return -ENOMEM;
smb->dev = &client->dev;
smb->id = id->driver_data;
i2c_set_clientdata(client, smb);
mutex_init(&smb->lock);
smb->dev = &client->dev;
smb->pdata = pdata;
smb347_dt_parse_dev_info(smb);
if (!smb->use_mains && !smb->use_usb)
return -EINVAL;
smb->regmap = devm_regmap_init_i2c(client, &smb347_regmap);
if (IS_ERR(smb->regmap))
return PTR_ERR(smb->regmap);
ret = smb347_hw_init(smb);
if (ret < 0)
return ret;
mains_usb_cfg.supplied_to = battery;
mains_usb_cfg.num_supplicants = ARRAY_SIZE(battery);
mains_usb_cfg.drv_data = smb;
if (smb->pdata->use_mains) {
smb->mains = power_supply_register(dev, &smb347_mains_desc,
mains_usb_cfg.of_node = dev->of_node;
if (smb->use_mains) {
smb->mains = devm_power_supply_register(dev, &smb347_mains_desc,
&mains_usb_cfg);
if (IS_ERR(smb->mains))
return PTR_ERR(smb->mains);
}
if (smb->pdata->use_usb) {
smb->usb = power_supply_register(dev, &smb347_usb_desc,
if (smb->use_usb) {
smb->usb = devm_power_supply_register(dev, &smb347_usb_desc,
&mains_usb_cfg);
if (IS_ERR(smb->usb)) {
if (smb->pdata->use_mains)
power_supply_unregister(smb->mains);
if (IS_ERR(smb->usb))
return PTR_ERR(smb->usb);
}
}
battery_cfg.drv_data = smb;
smb->battery = power_supply_register(dev, &smb347_battery_desc,
&battery_cfg);
if (IS_ERR(smb->battery)) {
if (smb->pdata->use_usb)
power_supply_unregister(smb->usb);
if (smb->pdata->use_mains)
power_supply_unregister(smb->mains);
return PTR_ERR(smb->battery);
}
ret = smb347_get_battery_info(smb);
if (ret)
return ret;
ret = smb347_hw_init(smb);
if (ret < 0)
return ret;
/*
* Interrupt pin is optional. If it is connected, we setup the
* interrupt support here.
*/
if (pdata->irq_gpio >= 0) {
if (client->irq) {
ret = smb347_irq_init(smb, client);
if (ret < 0) {
dev_warn(dev, "failed to initialize IRQ: %d\n", ret);
......@@ -1299,29 +1357,31 @@ static int smb347_remove(struct i2c_client *client)
{
struct smb347_charger *smb = i2c_get_clientdata(client);
if (client->irq) {
if (client->irq)
smb347_irq_disable(smb);
free_irq(client->irq, smb);
gpio_free(smb->pdata->irq_gpio);
}
power_supply_unregister(smb->battery);
if (smb->pdata->use_usb)
power_supply_unregister(smb->usb);
if (smb->pdata->use_mains)
power_supply_unregister(smb->mains);
return 0;
}
static const struct i2c_device_id smb347_id[] = {
{ "smb347", 0 },
{ }
{ "smb345", SMB345 },
{ "smb347", SMB347 },
{ "smb358", SMB358 },
{ },
};
MODULE_DEVICE_TABLE(i2c, smb347_id);
static const struct of_device_id smb3xx_of_match[] = {
{ .compatible = "summit,smb345" },
{ .compatible = "summit,smb347" },
{ .compatible = "summit,smb358" },
{ },
};
MODULE_DEVICE_TABLE(of, smb3xx_of_match);
static struct i2c_driver smb347_driver = {
.driver = {
.name = "smb347",
.of_match_table = smb3xx_of_match,
},
.probe = smb347_probe,
.remove = smb347_remove,
......
......@@ -352,8 +352,8 @@ static int param_set_ac_online(const char *key, const struct kernel_param *kp)
static int param_get_ac_online(char *buffer, const struct kernel_param *kp)
{
strcpy(buffer, map_get_key(map_ac_online, ac_online, "unknown"));
return strlen(buffer);
return sprintf(buffer, "%s\n",
map_get_key(map_ac_online, ac_online, "unknown"));
}
static int param_set_usb_online(const char *key, const struct kernel_param *kp)
......@@ -365,8 +365,8 @@ static int param_set_usb_online(const char *key, const struct kernel_param *kp)
static int param_get_usb_online(char *buffer, const struct kernel_param *kp)
{
strcpy(buffer, map_get_key(map_ac_online, usb_online, "unknown"));
return strlen(buffer);
return sprintf(buffer, "%s\n",
map_get_key(map_ac_online, usb_online, "unknown"));
}
static int param_set_battery_status(const char *key,
......@@ -379,8 +379,8 @@ static int param_set_battery_status(const char *key,
static int param_get_battery_status(char *buffer, const struct kernel_param *kp)
{
strcpy(buffer, map_get_key(map_status, battery_status, "unknown"));
return strlen(buffer);
return sprintf(buffer, "%s\n",
map_get_key(map_ac_online, battery_status, "unknown"));
}
static int param_set_battery_health(const char *key,
......@@ -393,8 +393,8 @@ static int param_set_battery_health(const char *key,
static int param_get_battery_health(char *buffer, const struct kernel_param *kp)
{
strcpy(buffer, map_get_key(map_health, battery_health, "unknown"));
return strlen(buffer);
return sprintf(buffer, "%s\n",
map_get_key(map_ac_online, battery_health, "unknown"));
}
static int param_set_battery_present(const char *key,
......@@ -408,8 +408,8 @@ static int param_set_battery_present(const char *key,
static int param_get_battery_present(char *buffer,
const struct kernel_param *kp)
{
strcpy(buffer, map_get_key(map_present, battery_present, "unknown"));
return strlen(buffer);
return sprintf(buffer, "%s\n",
map_get_key(map_ac_online, battery_present, "unknown"));
}
static int param_set_battery_technology(const char *key,
......@@ -424,9 +424,9 @@ static int param_set_battery_technology(const char *key,
static int param_get_battery_technology(char *buffer,
const struct kernel_param *kp)
{
strcpy(buffer,
map_get_key(map_technology, battery_technology, "unknown"));
return strlen(buffer);
return sprintf(buffer, "%s\n",
map_get_key(map_ac_online, battery_technology,
"unknown"));
}
static int param_set_battery_capacity(const char *key,
......
......@@ -38,6 +38,7 @@
/* Interrupt Status */
#define UCS1002_REG_INTERRUPT_STATUS 0x10
# define F_ERR BIT(7)
# define F_DISCHARGE_ERR BIT(6)
# define F_RESET BIT(5)
# define F_MIN_KEEP_OUT BIT(4)
......@@ -103,6 +104,9 @@ struct ucs1002_info {
struct regulator_dev *rdev;
bool present;
bool output_disable;
struct delayed_work health_poll;
int health;
};
static enum power_supply_property ucs1002_props[] = {
......@@ -362,32 +366,6 @@ static int ucs1002_get_usb_type(struct ucs1002_info *info,
return 0;
}
static int ucs1002_get_health(struct ucs1002_info *info,
union power_supply_propval *val)
{
unsigned int reg;
int ret, health;
ret = regmap_read(info->regmap, UCS1002_REG_INTERRUPT_STATUS, &reg);
if (ret)
return ret;
if (reg & F_TSD)
health = POWER_SUPPLY_HEALTH_OVERHEAT;
else if (reg & (F_OVER_VOLT | F_BACK_VOLT))
health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
else if (reg & F_OVER_ILIM)
health = POWER_SUPPLY_HEALTH_OVERCURRENT;
else if (reg & (F_DISCHARGE_ERR | F_MIN_KEEP_OUT))
health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
else
health = POWER_SUPPLY_HEALTH_GOOD;
val->intval = health;
return 0;
}
static int ucs1002_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
......@@ -406,7 +384,7 @@ static int ucs1002_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_USB_TYPE:
return ucs1002_get_usb_type(info, val);
case POWER_SUPPLY_PROP_HEALTH:
return ucs1002_get_health(info, val);
return val->intval = info->health;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = info->present;
return 0;
......@@ -458,6 +436,38 @@ static const struct power_supply_desc ucs1002_charger_desc = {
.num_properties = ARRAY_SIZE(ucs1002_props),
};
static void ucs1002_health_poll(struct work_struct *work)
{
struct ucs1002_info *info = container_of(work, struct ucs1002_info,
health_poll.work);
int ret;
u32 reg;
ret = regmap_read(info->regmap, UCS1002_REG_INTERRUPT_STATUS, &reg);
if (ret)
return;
/* bad health and no status change, just schedule us again in a while */
if ((reg & F_ERR) && info->health != POWER_SUPPLY_HEALTH_GOOD) {
schedule_delayed_work(&info->health_poll,
msecs_to_jiffies(2000));
return;
}
if (reg & F_TSD)
info->health = POWER_SUPPLY_HEALTH_OVERHEAT;
else if (reg & (F_OVER_VOLT | F_BACK_VOLT))
info->health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
else if (reg & F_OVER_ILIM)
info->health = POWER_SUPPLY_HEALTH_OVERCURRENT;
else if (reg & (F_DISCHARGE_ERR | F_MIN_KEEP_OUT))
info->health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
else
info->health = POWER_SUPPLY_HEALTH_GOOD;
sysfs_notify(&info->charger->dev.kobj, NULL, "health");
}
static irqreturn_t ucs1002_charger_irq(int irq, void *data)
{
int ret, regval;
......@@ -484,7 +494,7 @@ static irqreturn_t ucs1002_alert_irq(int irq, void *data)
{
struct ucs1002_info *info = data;
power_supply_changed(info->charger);
mod_delayed_work(system_wq, &info->health_poll, 0);
return IRQ_HANDLED;
}
......@@ -632,6 +642,9 @@ static int ucs1002_probe(struct i2c_client *client,
return ret;
}
info->health = POWER_SUPPLY_HEALTH_GOOD;
INIT_DELAYED_WORK(&info->health_poll, ucs1002_health_poll);
if (irq_a_det > 0) {
ret = devm_request_threaded_irq(dev, irq_a_det, NULL,
ucs1002_charger_irq,
......@@ -645,10 +658,8 @@ static int ucs1002_probe(struct i2c_client *client,
}
if (irq_alert > 0) {
ret = devm_request_threaded_irq(dev, irq_alert, NULL,
ucs1002_alert_irq,
IRQF_ONESHOT,
"ucs1002-alert", info);
ret = devm_request_irq(dev, irq_alert, ucs1002_alert_irq,
0,"ucs1002-alert", info);
if (ret) {
dev_err(dev, "Failed to request ALERT threaded irq: %d\n",
ret);
......
/* SPDX-License-Identifier: (GPL-2.0-or-later or MIT) */
/*
* Author: David Heidelberg <david@ixit.cz>
*/
#ifndef _DT_BINDINGS_SMB347_CHARGER_H
#define _DT_BINDINGS_SMB347_CHARGER_H
/* Charging compensation method */
#define SMB3XX_SOFT_TEMP_COMPENSATE_NONE 0
#define SMB3XX_SOFT_TEMP_COMPENSATE_CURRENT 1
#define SMB3XX_SOFT_TEMP_COMPENSATE_VOLTAGE 2
/* Charging enable control */
#define SMB3XX_CHG_ENABLE_SW 0
#define SMB3XX_CHG_ENABLE_PIN_ACTIVE_LOW 1
#define SMB3XX_CHG_ENABLE_PIN_ACTIVE_HIGH 2
#endif
......@@ -32,6 +32,7 @@ enum bq27xxx_chip {
BQ27621,
BQ27Z561,
BQ28Z610,
BQ34Z100,
};
struct bq27xxx_device_info;
......
......@@ -31,22 +31,16 @@ enum polling_modes {
CM_POLL_CHARGING_ONLY,
};
enum cm_event_types {
CM_EVENT_UNKNOWN = 0,
CM_EVENT_BATT_FULL,
CM_EVENT_BATT_IN,
CM_EVENT_BATT_OUT,
CM_EVENT_BATT_OVERHEAT,
CM_EVENT_BATT_COLD,
CM_EVENT_EXT_PWR_IN_OUT,
CM_EVENT_CHG_START_STOP,
CM_EVENT_OTHERS,
enum cm_batt_temp {
CM_BATT_OK = 0,
CM_BATT_OVERHEAT,
CM_BATT_COLD,
};
/**
* struct charger_cable
* @extcon_name: the name of extcon device.
* @name: the name of charger cable(external connector).
* @name: the name of the cable connector
* @extcon_dev: the extcon device.
* @wq: the workqueue to control charger according to the state of
* charger cable. If charger cable is attached, enable charger.
......@@ -62,9 +56,10 @@ enum cm_event_types {
struct charger_cable {
const char *extcon_name;
const char *name;
struct extcon_dev *extcon_dev;
u64 extcon_type;
/* The charger-manager use Extcon framework */
struct extcon_specific_cable_nb extcon_dev;
struct work_struct wq;
struct notifier_block nb;
......@@ -131,11 +126,10 @@ struct charger_regulator {
* @psy_name: the name of power-supply-class for charger manager
* @polling_mode:
* Determine which polling mode will be used
* @fullbatt_vchkdrop_ms:
* @fullbatt_vchkdrop_uV:
* Check voltage drop after the battery is fully charged.
* If it has dropped more than fullbatt_vchkdrop_uV after
* fullbatt_vchkdrop_ms, CM will restart charging.
* If it has dropped more than fullbatt_vchkdrop_uV
* CM will restart charging.
* @fullbatt_uV: voltage in microvolt
* If VBATT >= fullbatt_uV, it is assumed to be full.
* @fullbatt_soc: state of Charge in %
......@@ -172,7 +166,6 @@ struct charger_desc {
enum polling_modes polling_mode;
unsigned int polling_interval_ms;
unsigned int fullbatt_vchkdrop_ms;
unsigned int fullbatt_vchkdrop_uV;
unsigned int fullbatt_uV;
unsigned int fullbatt_soc;
......@@ -211,9 +204,6 @@ struct charger_desc {
* @charger_stat: array of power_supply for chargers
* @tzd_batt : thermal zone device for battery
* @charger_enabled: the state of charger
* @fullbatt_vchk_jiffies_at:
* jiffies at the time full battery check will occur.
* @fullbatt_vchk_work: work queue for full battery check
* @emergency_stop:
* When setting true, stop charging
* @psy_name_buf: the name of power-supply-class for charger manager
......@@ -224,6 +214,7 @@ struct charger_desc {
* saved status of battery before entering suspend-to-RAM
* @charging_start_time: saved start time of enabling charging
* @charging_end_time: saved end time of disabling charging
* @battery_status: Current battery status
*/
struct charger_manager {
struct list_head entry;
......@@ -235,9 +226,6 @@ struct charger_manager {
#endif
bool charger_enabled;
unsigned long fullbatt_vchk_jiffies_at;
struct delayed_work fullbatt_vchk_work;
int emergency_stop;
char psy_name_buf[PSY_NAME_MAX + 1];
......@@ -246,13 +234,8 @@ struct charger_manager {
u64 charging_start_time;
u64 charging_end_time;
int battery_status;
};
#if IS_ENABLED(CONFIG_CHARGER_MANAGER)
extern void cm_notify_event(struct power_supply *psy,
enum cm_event_types type, char *msg);
#else
static inline void cm_notify_event(struct power_supply *psy,
enum cm_event_types type, char *msg) { }
#endif
#endif /* _CHARGER_MANAGER_H */
......@@ -13,18 +13,12 @@
* struct gpio_charger_platform_data - platform_data for gpio_charger devices
* @name: Name for the chargers power_supply device
* @type: Type of the charger
* @gpio: GPIO which is used to indicate the chargers status
* @gpio_active_low: Should be set to 1 if the GPIO is active low otherwise 0
* @supplied_to: Array of battery names to which this chargers supplies power
* @num_supplicants: Number of entries in the supplied_to array
*/
struct gpio_charger_platform_data {
const char *name;
enum power_supply_type type;
int gpio;
int gpio_active_low;
char **supplied_to;
size_t num_supplicants;
};
......
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Summit Microelectronics SMB347 Battery Charger Driver
*
* Copyright (C) 2011, Intel Corporation
*
* Authors: Bruce E. Robertson <bruce.e.robertson@intel.com>
* Mika Westerberg <mika.westerberg@linux.intel.com>
*/
#ifndef SMB347_CHARGER_H
#define SMB347_CHARGER_H
#include <linux/types.h>
#include <linux/power_supply.h>
enum {
/* use the default compensation method */
SMB347_SOFT_TEMP_COMPENSATE_DEFAULT = -1,
SMB347_SOFT_TEMP_COMPENSATE_NONE,
SMB347_SOFT_TEMP_COMPENSATE_CURRENT,
SMB347_SOFT_TEMP_COMPENSATE_VOLTAGE,
};
/* Use default factory programmed value for hard/soft temperature limit */
#define SMB347_TEMP_USE_DEFAULT -273
/*
* Charging enable can be controlled by software (via i2c) by
* smb347-charger driver or by EN pin (active low/high).
*/
enum smb347_chg_enable {
SMB347_CHG_ENABLE_SW,
SMB347_CHG_ENABLE_PIN_ACTIVE_LOW,
SMB347_CHG_ENABLE_PIN_ACTIVE_HIGH,
};
/**
* struct smb347_charger_platform_data - platform data for SMB347 charger
* @battery_info: Information about the battery
* @max_charge_current: maximum current (in uA) the battery can be charged
* @max_charge_voltage: maximum voltage (in uV) the battery can be charged
* @pre_charge_current: current (in uA) to use in pre-charging phase
* @termination_current: current (in uA) used to determine when the
* charging cycle terminates
* @pre_to_fast_voltage: voltage (in uV) treshold used for transitioning to
* pre-charge to fast charge mode
* @mains_current_limit: maximum input current drawn from AC/DC input (in uA)
* @usb_hc_current_limit: maximum input high current (in uA) drawn from USB
* input
* @chip_temp_threshold: die temperature where device starts limiting charge
* current [%100 - %130] (in degree C)
* @soft_cold_temp_limit: soft cold temperature limit [%0 - %15] (in degree C),
* granularity is 5 deg C.
* @soft_hot_temp_limit: soft hot temperature limit [%40 - %55] (in degree C),
* granularity is 5 deg C.
* @hard_cold_temp_limit: hard cold temperature limit [%-5 - %10] (in degree C),
* granularity is 5 deg C.
* @hard_hot_temp_limit: hard hot temperature limit [%50 - %65] (in degree C),
* granularity is 5 deg C.
* @suspend_on_hard_temp_limit: suspend charging when hard limit is hit
* @soft_temp_limit_compensation: compensation method when soft temperature
* limit is hit
* @charge_current_compensation: current (in uA) for charging compensation
* current when temperature hits soft limits
* @use_mains: AC/DC input can be used
* @use_usb: USB input can be used
* @use_usb_otg: USB OTG output can be used (not implemented yet)
* @irq_gpio: GPIO number used for interrupts (%-1 if not used)
* @enable_control: how charging enable/disable is controlled
* (driver/pin controls)
*
* @use_main, @use_usb, and @use_usb_otg are means to enable/disable
* hardware support for these. This is useful when we want to have for
* example OTG charging controlled via OTG transceiver driver and not by
* the SMB347 hardware.
*
* Hard and soft temperature limit values are given as described in the
* device data sheet and assuming NTC beta value is %3750. Even if this is
* not the case, these values should be used. They can be mapped to the
* corresponding NTC beta values with the help of table %2 in the data
* sheet. So for example if NTC beta is %3375 and we want to program hard
* hot limit to be %53 deg C, @hard_hot_temp_limit should be set to %50.
*
* If zero value is given in any of the current and voltage values, the
* factory programmed default will be used. For soft/hard temperature
* values, pass in %SMB347_TEMP_USE_DEFAULT instead.
*/
struct smb347_charger_platform_data {
struct power_supply_info battery_info;
unsigned int max_charge_current;
unsigned int max_charge_voltage;
unsigned int pre_charge_current;
unsigned int termination_current;
unsigned int pre_to_fast_voltage;
unsigned int mains_current_limit;
unsigned int usb_hc_current_limit;
unsigned int chip_temp_threshold;
int soft_cold_temp_limit;
int soft_hot_temp_limit;
int hard_cold_temp_limit;
int hard_hot_temp_limit;
bool suspend_on_hard_temp_limit;
unsigned int soft_temp_limit_compensation;
unsigned int charge_current_compensation;
bool use_mains;
bool use_usb;
bool use_usb_otg;
int irq_gpio;
enum smb347_chg_enable enable_control;
};
#endif /* SMB347_CHARGER_H */
......@@ -186,6 +186,7 @@ enum power_supply_type {
POWER_SUPPLY_TYPE_USB_PD, /* Power Delivery Port */
POWER_SUPPLY_TYPE_USB_PD_DRP, /* PD Dual Role Port */
POWER_SUPPLY_TYPE_APPLE_BRICK_ID, /* Apple Charging Method */
POWER_SUPPLY_TYPE_WIRELESS, /* Wireless */
};
enum power_supply_usb_type {
......@@ -365,6 +366,12 @@ struct power_supply_battery_info {
int constant_charge_voltage_max_uv; /* microVolts */
int factory_internal_resistance_uohm; /* microOhms */
int ocv_temp[POWER_SUPPLY_OCV_TEMP_MAX];/* celsius */
int temp_ambient_alert_min; /* celsius */
int temp_ambient_alert_max; /* celsius */
int temp_alert_min; /* celsius */
int temp_alert_max; /* celsius */
int temp_min; /* celsius */
int temp_max; /* celsius */
struct power_supply_battery_ocv_table *ocv_table[POWER_SUPPLY_OCV_TEMP_MAX];
int ocv_table_size[POWER_SUPPLY_OCV_TEMP_MAX];
struct power_supply_resistance_temp_table *resist_table;
......
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