power supply and reset changes for the v5.14 series

battery/charger driver changes:
  * convert charger-manager binding to YAML
  * drop bd70528-charger driver
  * drop pm2301-charger driver
  * introduce rt5033-battery driver
  * misc. improvements and fixes
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEE72YNB0Y/i3JqeVQT2O7X88g7+poFAmDdZH0ACgkQ2O7X88g7
 +pqLpxAAixSqw2UrKCipLUaWWH1vanwPeU78z8XEzpHdNayrTR9PujBMhWH+ps+R
 IgHNjsQuJsnvbedWfmzkgjrZe5amAuN6Va8OkVDSVzEY+RZvLmXfj9POC5d4LmNK
 wpIBM/Okjie097j3ZWz7CJp47rsQnkS9EvRY4FevNjz1zt1VSpQNyDHAjemsn+j9
 1F9BnBMr2gzgTMxLDIloa71VMEaA8cZlAWulIKwxaN5FaSwoacK0NfestjM1R/Bc
 Z50pfqzAXBrofm14WKQRIgSEkf9zM6S1AjHG/y4b/C69XbzmBJxn+FK1F+yukWfN
 Kiq1kGvE9zWqOrDYPd7LxYDetTJ9JgNDxMreLIP7N+syurFYizl7v0JU9TL6BKsh
 EXCklOx/xaCs0Y3a3kBH8dJ72MBppW1loE0XbAqhkMDkzQybTxg9xUI1gnhVTIlb
 5b6KbU5kUo4AghJ279zSNxYNHABKxyu/WCZKNafsLT9pC41muGIPT8ZNgAs2Z3fG
 V60RSiAFpRrHU7efuizbXYchLEv+vWJG7kfdWmHWawMIqE11cUHgMIjFvkv/41t/
 zD3ZnlvvQe/ZZFO16aasvpj99BsZkI5tmNjmVnfPc2cOsLw/HFHIObSe+7x2PpVe
 7peflSz6DHL48NAsoq71WxeoPVPm3YYMMA9kr4tt9ZnVBbLJaCM=
 =EFOr
 -----END PGP SIGNATURE-----

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

Pull power supply and reset updates from Sebastian Reichel:
 "Battery/charger driver changes:

   - convert charger-manager binding to YAML

   - drop bd70528-charger driver

   - drop pm2301-charger driver

   - introduce rt5033-battery driver

   - misc improvements and fixes"

* tag 'for-v5.14' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (42 commits)
  power: supply: ab8500: Fix an old bug
  power: supply: axp288_fuel_gauge: remove redundant continue statement
  power: supply: axp288_fuel_gauge: Make "T3 MRD" no_battery_list DMI entry more generic
  power: supply: axp288_fuel_gauge: Rename fuel_gauge_blacklist to no_battery_list
  power: supply: bq24190_charger: drop of_match_ptr() from device ID table
  drivers: power: add missing MODULE_DEVICE_TABLE in keystone-reset.c
  power: supply: ab8500: add missing MODULE_DEVICE_TABLE
  power: supply: charger-manager: add missing MODULE_DEVICE_TABLE
  power: reset: regulator-poweroff: add missing MODULE_DEVICE_TABLE
  power: supply: cpcap-charger: get the battery inserted infomation from cpcap-battery
  power: supply: cpcap-battery: invalidate config when incompatible measurements are read
  power: supply: axp20x_battery: allow disabling battery charging
  power: supply: max17040: drop unused platform data support
  power: supply: max17040: simplify POWER_SUPPLY_PROP_ONLINE
  power: supply: max17040: remove non-working POWER_SUPPLY_PROP_STATUS
  power: reset: at91-sama5d2_shdwc: Remove redundant error printing in at91_shdwc_probe()
  power: reset: gpio-poweroff: add missing MODULE_DEVICE_TABLE
  power: supply: rt5033_battery: Fix device tree enumeration
  dt-bindings: power: supply: Add DT schema for richtek,rt5033-battery
  power: supply: Drop BD70528 support
  ...
This commit is contained in:
Linus Torvalds 2021-07-07 13:17:48 -07:00
commit c6e8c51f69
38 changed files with 1123 additions and 2672 deletions

View File

@ -1,91 +0,0 @@
charger-manager bindings
~~~~~~~~~~~~~~~~~~~~~~~~
Required properties :
- compatible : "charger-manager"
- <>-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 - 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 - 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
-cold-in-minus : flag that cold temperature is in minus degrees
-hot : critical hot temperature of battery for charging
-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";
chg-reg-supply = <&charger_regulator>;
cm-name = "battery";
/* Always polling ON : 30s */
cm-poll-mode = <1>;
cm-poll-interval = <30000>;
cm-fullbatt-vchkdrop-volt = <150000>;
cm-fullbatt-soc = <100>;
cm-battery-stat = <3>;
cm-chargers = "charger0", "charger1", "charger2";
cm-fuel-gauge = "fuelgauge0";
cm-thermal-zone = "thermal_zone.1"
/* in deci centigrade */
cm-battery-cold = <50>;
cm-battery-cold-in-minus;
cm-battery-hot = <800>;
cm-battery-temp-diff = <100>;
/* Allow charging for 5hr */
cm-charging-max = <18000000>;
/* Allow discharging for 2hr */
cm-discharging-max = <7200000>;
regulator@0 {
cm-regulator-name = "chg-reg";
cable@0 {
cm-cable-name = "USB";
cm-cable-extcon = "extcon-dev.0";
cm-cable-min = <475000>;
cm-cable-max = <500000>;
};
cable@1 {
cm-cable-name = "SDP";
cm-cable-extcon = "extcon-dev.0";
cm-cable-min = <650000>;
cm-cable-max = <675000>;
};
};
};

View File

@ -0,0 +1,215 @@
# SPDX-License-Identifier: GPL-2.0
%YAML 1.2
---
$id: http://devicetree.org/schemas/power/supply/charger-manager.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Charger Manager
maintainers:
- Sebastian Reichel <sre@kernel.org>
description: |
Binding for the legacy charger manager driver.
Please do not use for new products.
properties:
compatible:
const: charger-manager
cm-chargers:
description: name of chargers
$ref: /schemas/types.yaml#/definitions/string-array
cm-num-chargers:
$ref: /schemas/types.yaml#/definitions/uint32
deprecated: true
cm-fuel-gauge:
description: name of battery fuel gauge
$ref: /schemas/types.yaml#/definitions/string
cm-name:
description: name of the charger manager
default: battery
$ref: /schemas/types.yaml#/definitions/string
cm-poll-mode:
description: polling mode
default: 0
enum:
- 0 # disabled
- 1 # always
- 2 # when external power is connected
- 3 # when charging
cm-poll-interval:
description: polling interval (in ms)
$ref: /schemas/types.yaml#/definitions/uint32
cm-battery-stat:
description: battery status
enum:
- 0 # battery always present
- 1 # no battery
- 2 # check presence via fuel gauge
- 3 # check presence via charger
cm-fullbatt-vchkdrop-volt:
description: voltage drop before restarting charging in uV
$ref: /schemas/types.yaml#/definitions/uint32
cm-fullbatt-vchkdrop-ms:
deprecated: true
cm-fullbatt-voltage:
description: voltage of full battery in uV
$ref: /schemas/types.yaml#/definitions/uint32
cm-fullbatt-soc:
description: state of charge to consider as full battery in %
$ref: /schemas/types.yaml#/definitions/uint32
cm-fullbatt-capacity:
description: capcity to consider as full battery in uAh
$ref: /schemas/types.yaml#/definitions/uint32
cm-thermal-zone:
description: name of external thermometer's thermal zone
$ref: /schemas/types.yaml#/definitions/string
cm-discharging-max:
description: limits of discharging duration in ms
$ref: /schemas/types.yaml#/definitions/uint32
cm-charging-max:
description: limits of charging duration in ms
$ref: /schemas/types.yaml#/definitions/uint32
cm-battery-cold:
description: critical cold temperature of battery for charging in deci-degree celsius
$ref: /schemas/types.yaml#/definitions/uint32
cm-battery-cold-in-minus:
description: if set cm-battery-cold temperature is in minus degrees
type: boolean
cm-battery-hot:
description: critical hot temperature of battery for charging in deci-degree celsius
$ref: /schemas/types.yaml#/definitions/uint32
cm-battery-temp-diff:
description: temperature difference to allow recharging in deci-degree celsius
$ref: /schemas/types.yaml#/definitions/uint32
patternProperties:
"-supply$":
description: regulator consumer, named according to cm-regulator-name
$ref: /schemas/types.yaml#/definitions/phandle
"^regulator[@-][0-9]$":
type: object
properties:
cm-regulator-name:
description: name of charger regulator
$ref: /schemas/types.yaml#/definitions/string
required:
- cm-regulator-name
additionalProperties: false
patternProperties:
"^cable[@-][0-9]$":
type: object
properties:
cm-cable-name:
description: name of charger cable
enum:
- USB
- USB-HOST
- SDP
- DCP
- CDP
- ACA
- FAST-CHARGER
- SLOW-CHARGER
- WPT
- PD
- DOCK
- JIG
- MECHANICAL
cm-cable-extcon:
description: name of extcon dev
$ref: /schemas/types.yaml#/definitions/string
cm-cable-min:
description: minimum current of cable in uA
$ref: /schemas/types.yaml#/definitions/uint32
cm-cable-max:
description: maximum current of cable in uA
$ref: /schemas/types.yaml#/definitions/uint32
required:
- cm-cable-name
- cm-cable-extcon
additionalProperties: false
required:
- compatible
- cm-chargers
- cm-fuel-gauge
additionalProperties: false
examples:
- |
charger-manager {
compatible = "charger-manager";
chg-reg-supply = <&charger_regulator>;
cm-name = "battery";
/* Always polling ON : 30s */
cm-poll-mode = <1>;
cm-poll-interval = <30000>;
cm-fullbatt-vchkdrop-volt = <150000>;
cm-fullbatt-soc = <100>;
cm-battery-stat = <3>;
cm-chargers = "charger0", "charger1", "charger2";
cm-fuel-gauge = "fuelgauge0";
cm-thermal-zone = "thermal_zone.1";
/* in deci centigrade */
cm-battery-cold = <50>;
cm-battery-cold-in-minus;
cm-battery-hot = <800>;
cm-battery-temp-diff = <100>;
/* Allow charging for 5hr */
cm-charging-max = <18000000>;
/* Allow discharging for 2hr */
cm-discharging-max = <7200000>;
regulator-0 {
cm-regulator-name = "chg-reg";
cable-0 {
cm-cable-name = "USB";
cm-cable-extcon = "extcon-dev.0";
cm-cable-min = <475000>;
cm-cable-max = <500000>;
};
cable-1 {
cm-cable-name = "SDP";
cm-cable-extcon = "extcon-dev.0";
cm-cable-min = <650000>;
cm-cable-max = <675000>;
};
};
};

View File

@ -89,7 +89,7 @@ examples:
reg = <0x36>;
maxim,alert-low-soc-level = <10>;
interrupt-parent = <&gpio7>;
interrupts = <2 IRQ_TYPE_EDGE_FALLING>;
interrupts = <2 IRQ_TYPE_LEVEL_LOW>;
wakeup-source;
};
};

View File

@ -0,0 +1,54 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: "http://devicetree.org/schemas/power/supply/richtek,rt5033-battery.yaml#"
$schema: "http://devicetree.org/meta-schemas/core.yaml#"
title: Richtek RT5033 PMIC Fuel Gauge
maintainers:
- Stephan Gerhold <stephan@gerhold.net>
allOf:
- $ref: power-supply.yaml#
properties:
compatible:
const: richtek,rt5033-battery
reg:
maxItems: 1
interrupts:
maxItems: 1
required:
- compatible
- reg
additionalProperties: false
examples:
- |
i2c {
#address-cells = <1>;
#size-cells = <0>;
battery@35 {
compatible = "richtek,rt5033-battery";
reg = <0x35>;
};
};
- |
#include <dt-bindings/interrupt-controller/irq.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
battery@35 {
compatible = "richtek,rt5033-battery";
reg = <0x35>;
interrupt-parent = <&msmgpio>;
interrupts = <121 IRQ_TYPE_EDGE_FALLING>;
};
};

View File

@ -14827,6 +14827,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git
F: Documentation/ABI/testing/sysfs-class-power
F: Documentation/devicetree/bindings/power/supply/
F: drivers/power/supply/
F: include/linux/power/
F: include/linux/power_supply.h
POWERNV OPERATOR PANEL LCD DISPLAY DRIVER
@ -16213,7 +16214,7 @@ W: http://www.ibm.com/developerworks/linux/linux390/
F: drivers/s390/scsi/zfcp_*
S3C ADC BATTERY DRIVER
M: Krzysztof Kozlowski <krzk@kernel.org>
M: Krzysztof Kozlowski <krzysztof.kozlowski@canonical.com>
L: linux-samsung-soc@vger.kernel.org
S: Odd Fixes
F: drivers/power/supply/s3c_adc_battery.c

View File

@ -351,10 +351,8 @@ static int __init at91_shdwc_probe(struct platform_device *pdev)
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
at91_shdwc->shdwc_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(at91_shdwc->shdwc_base)) {
dev_err(&pdev->dev, "Could not map reset controller address\n");
if (IS_ERR(at91_shdwc->shdwc_base))
return PTR_ERR(at91_shdwc->shdwc_base);
}
match = of_match_node(at91_shdwc_of_match, pdev->dev.of_node);
at91_shdwc->rcfg = match->data;

View File

@ -90,6 +90,7 @@ static const struct of_device_id of_gpio_poweroff_match[] = {
{ .compatible = "gpio-poweroff", },
{},
};
MODULE_DEVICE_TABLE(of, of_gpio_poweroff_match);
static struct platform_driver gpio_poweroff_driver = {
.probe = gpio_poweroff_probe,

View File

@ -71,6 +71,7 @@ static const struct of_device_id rsctrl_of_match[] = {
{.compatible = "ti,keystone-reset", },
{},
};
MODULE_DEVICE_TABLE(of, rsctrl_of_match);
static int rsctrl_probe(struct platform_device *pdev)
{

View File

@ -64,6 +64,7 @@ static const struct of_device_id of_regulator_poweroff_match[] = {
{ .compatible = "regulator-poweroff", },
{},
};
MODULE_DEVICE_TABLE(of, of_regulator_poweroff_match);
static struct platform_driver regulator_poweroff_driver = {
.probe = regulator_poweroff_probe,

View File

@ -712,7 +712,8 @@ config BATTERY_GOLDFISH
config BATTERY_RT5033
tristate "RT5033 fuel gauge support"
depends on MFD_RT5033
depends on I2C
select REGMAP_I2C
help
This adds support for battery fuel gauge in Richtek RT5033 PMIC.
The fuelgauge calculates and determines the battery state of charge
@ -760,15 +761,6 @@ config CHARGER_UCS1002
Say Y to enable support for Microchip UCS1002 Programmable
USB Port Power Controller with Charger Emulation.
config CHARGER_BD70528
tristate "ROHM bd70528 charger driver"
depends on MFD_ROHM_BD70528
select LINEAR_RANGES
help
Say Y here to enable support for getting battery status
information and altering charger configurations from charger
block of the ROHM BD70528 Power Management IC.
config CHARGER_BD99954
tristate "ROHM bd99954 charger driver"
depends on I2C

View File

@ -60,7 +60,7 @@ obj-$(CONFIG_BATTERY_TWL4030_MADC) += twl4030_madc_battery.o
obj-$(CONFIG_CHARGER_88PM860X) += 88pm860x_charger.o
obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
obj-$(CONFIG_BATTERY_RX51) += rx51_battery.o
obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o abx500_chargalg.o pm2301_charger.o
obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o abx500_chargalg.o
obj-$(CONFIG_CHARGER_CPCAP) += cpcap-charger.o
obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o
obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o
@ -96,7 +96,6 @@ obj-$(CONFIG_CHARGER_CROS_USBPD) += cros_usbpd-charger.o
obj-$(CONFIG_CHARGER_SC2731) += sc2731_charger.o
obj-$(CONFIG_FUEL_GAUGE_SC27XX) += sc27xx_fuel_gauge.o
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

View File

@ -506,9 +506,6 @@ struct abx500_bm_data {
int usb_safety_tmr_h;
int bkup_bat_v;
int bkup_bat_i;
bool autopower_cfg;
bool ac_enabled;
bool usb_enabled;
bool no_maintenance;
bool capacity_scaling;
bool chg_unknown_bat;
@ -730,4 +727,8 @@ int ab8500_bm_of_probe(struct device *dev,
struct device_node *np,
struct abx500_bm_data *bm);
extern struct platform_driver ab8500_fg_driver;
extern struct platform_driver ab8500_btemp_driver;
extern struct platform_driver abx500_chargalg_driver;
#endif /* _AB8500_CHARGER_H_ */

View File

@ -15,7 +15,7 @@
* - POWER_SUPPLY_TYPE_USB,
* because only them store as drv_data pointer to struct ux500_charger.
*/
#define psy_to_ux500_charger(x) power_supply_get_drvdata(psy)
#define psy_to_ux500_charger(x) power_supply_get_drvdata(x)
/* Forward declaration */
struct ux500_charger;

View File

@ -13,6 +13,7 @@
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/component.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/slab.h>
@ -932,26 +933,6 @@ static int __maybe_unused ab8500_btemp_suspend(struct device *dev)
return 0;
}
static int ab8500_btemp_remove(struct platform_device *pdev)
{
struct ab8500_btemp *di = platform_get_drvdata(pdev);
int i, irq;
/* Disable interrupts */
for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) {
irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name);
free_irq(irq, di);
}
/* Delete the work queue */
destroy_workqueue(di->btemp_wq);
flush_scheduled_work();
power_supply_unregister(di->btemp_psy);
return 0;
}
static char *supply_interface[] = {
"ab8500_chargalg",
"ab8500_fg",
@ -966,9 +947,42 @@ static const struct power_supply_desc ab8500_btemp_desc = {
.external_power_changed = ab8500_btemp_external_power_changed,
};
static int ab8500_btemp_bind(struct device *dev, struct device *master,
void *data)
{
struct ab8500_btemp *di = dev_get_drvdata(dev);
/* Create a work queue for the btemp */
di->btemp_wq =
alloc_workqueue("ab8500_btemp_wq", WQ_MEM_RECLAIM, 0);
if (di->btemp_wq == NULL) {
dev_err(dev, "failed to create work queue\n");
return -ENOMEM;
}
/* Kick off periodic temperature measurements */
ab8500_btemp_periodic(di, true);
return 0;
}
static void ab8500_btemp_unbind(struct device *dev, struct device *master,
void *data)
{
struct ab8500_btemp *di = dev_get_drvdata(dev);
/* Delete the work queue */
destroy_workqueue(di->btemp_wq);
flush_scheduled_work();
}
static const struct component_ops ab8500_btemp_component_ops = {
.bind = ab8500_btemp_bind,
.unbind = ab8500_btemp_unbind,
};
static int ab8500_btemp_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct power_supply_config psy_cfg = {};
struct device *dev = &pdev->dev;
struct ab8500_btemp *di;
@ -981,12 +995,6 @@ static int ab8500_btemp_probe(struct platform_device *pdev)
di->bm = &ab8500_bm_data;
ret = ab8500_bm_of_probe(dev, np, di->bm);
if (ret) {
dev_err(dev, "failed to get battery information\n");
return ret;
}
/* get parent data */
di->dev = dev;
di->parent = dev_get_drvdata(pdev->dev.parent);
@ -1011,14 +1019,6 @@ static int ab8500_btemp_probe(struct platform_device *pdev)
psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface);
psy_cfg.drv_data = di;
/* Create a work queue for the btemp */
di->btemp_wq =
alloc_workqueue("ab8500_btemp_wq", WQ_MEM_RECLAIM, 0);
if (di->btemp_wq == NULL) {
dev_err(dev, "failed to create work queue\n");
return -ENOMEM;
}
/* Init work for measuring temperature periodically */
INIT_DEFERRABLE_WORK(&di->btemp_periodic_work,
ab8500_btemp_periodic_work);
@ -1031,7 +1031,7 @@ static int ab8500_btemp_probe(struct platform_device *pdev)
AB8500_BTEMP_HIGH_TH, &val);
if (ret < 0) {
dev_err(dev, "%s ab8500 read failed\n", __func__);
goto free_btemp_wq;
return ret;
}
switch (val) {
case BTEMP_HIGH_TH_57_0:
@ -1050,30 +1050,28 @@ static int ab8500_btemp_probe(struct platform_device *pdev)
}
/* Register BTEMP power supply class */
di->btemp_psy = power_supply_register(dev, &ab8500_btemp_desc,
&psy_cfg);
di->btemp_psy = devm_power_supply_register(dev, &ab8500_btemp_desc,
&psy_cfg);
if (IS_ERR(di->btemp_psy)) {
dev_err(dev, "failed to register BTEMP psy\n");
ret = PTR_ERR(di->btemp_psy);
goto free_btemp_wq;
return PTR_ERR(di->btemp_psy);
}
/* Register interrupts */
for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) {
irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name);
if (irq < 0) {
ret = irq;
goto free_irq;
}
if (irq < 0)
return irq;
ret = request_threaded_irq(irq, NULL, ab8500_btemp_irq[i].isr,
ret = devm_request_threaded_irq(dev, irq, NULL,
ab8500_btemp_irq[i].isr,
IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT,
ab8500_btemp_irq[i].name, di);
if (ret) {
dev_err(dev, "failed to request %s IRQ %d: %d\n"
, ab8500_btemp_irq[i].name, irq, ret);
goto free_irq;
return ret;
}
dev_dbg(dev, "Requested %s IRQ %d: %d\n",
ab8500_btemp_irq[i].name, irq, ret);
@ -1081,23 +1079,16 @@ static int ab8500_btemp_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, di);
/* Kick off periodic temperature measurements */
ab8500_btemp_periodic(di, true);
list_add_tail(&di->node, &ab8500_btemp_list);
return ret;
return component_add(dev, &ab8500_btemp_component_ops);
}
free_irq:
/* We also have to free all successfully registered irqs */
for (i = i - 1; i >= 0; i--) {
irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name);
free_irq(irq, di);
}
static int ab8500_btemp_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &ab8500_btemp_component_ops);
power_supply_unregister(di->btemp_psy);
free_btemp_wq:
destroy_workqueue(di->btemp_wq);
return ret;
return 0;
}
static SIMPLE_DEV_PM_OPS(ab8500_btemp_pm_ops, ab8500_btemp_suspend, ab8500_btemp_resume);
@ -1106,8 +1097,9 @@ static const struct of_device_id ab8500_btemp_match[] = {
{ .compatible = "stericsson,ab8500-btemp", },
{ },
};
MODULE_DEVICE_TABLE(of, ab8500_btemp_match);
static struct platform_driver ab8500_btemp_driver = {
struct platform_driver ab8500_btemp_driver = {
.probe = ab8500_btemp_probe,
.remove = ab8500_btemp_remove,
.driver = {
@ -1116,20 +1108,6 @@ static struct platform_driver ab8500_btemp_driver = {
.pm = &ab8500_btemp_pm_ops,
},
};
static int __init ab8500_btemp_init(void)
{
return platform_driver_register(&ab8500_btemp_driver);
}
static void __exit ab8500_btemp_exit(void)
{
platform_driver_unregister(&ab8500_btemp_driver);
}
device_initcall(ab8500_btemp_init);
module_exit(ab8500_btemp_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy");
MODULE_ALIAS("platform:ab8500-btemp");

View File

@ -13,6 +13,7 @@
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/component.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/notifier.h>
@ -414,6 +415,14 @@ disable_otp:
static void ab8500_power_supply_changed(struct ab8500_charger *di,
struct power_supply *psy)
{
/*
* This happens if we get notifications or interrupts and
* the platform has been configured not to support one or
* other type of charging.
*/
if (!psy)
return;
if (di->autopower_cfg) {
if (!di->usb.charger_connected &&
!di->ac.charger_connected &&
@ -440,7 +449,15 @@ static void ab8500_charger_set_usb_connected(struct ab8500_charger *di,
if (!connected)
di->flags.vbus_drop_end = false;
sysfs_notify(&di->usb_chg.psy->dev.kobj, NULL, "present");
/*
* Sometimes the platform is configured not to support
* USB charging and no psy has been created, but we still
* will get these notifications.
*/
if (di->usb_chg.psy) {
sysfs_notify(&di->usb_chg.psy->dev.kobj, NULL,
"present");
}
if (connected) {
mutex_lock(&di->charger_attached_mutex);
@ -3171,9 +3188,6 @@ static int ab8500_charger_usb_notifier_call(struct notifier_block *nb,
enum ab8500_usb_state bm_usb_state;
unsigned mA = *((unsigned *)power);
if (!di)
return NOTIFY_DONE;
if (event != USB_EVENT_VBUS) {
dev_dbg(di->dev, "not a standard host, returning\n");
return NOTIFY_DONE;
@ -3276,50 +3290,6 @@ static struct notifier_block charger_nb = {
.notifier_call = ab8500_external_charger_prepare,
};
static int ab8500_charger_remove(struct platform_device *pdev)
{
struct ab8500_charger *di = platform_get_drvdata(pdev);
int i, irq, ret;
/* Disable AC charging */
ab8500_charger_ac_en(&di->ac_chg, false, 0, 0);
/* Disable USB charging */
ab8500_charger_usb_en(&di->usb_chg, false, 0, 0);
/* Disable interrupts */
for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) {
irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name);
free_irq(irq, di);
}
/* Backup battery voltage and current disable */
ret = abx500_mask_and_set_register_interruptible(di->dev,
AB8500_RTC, AB8500_RTC_CTRL_REG, RTC_BUP_CH_ENA, 0);
if (ret < 0)
dev_err(di->dev, "%s mask and set failed\n", __func__);
usb_unregister_notifier(di->usb_phy, &di->nb);
usb_put_phy(di->usb_phy);
/* Delete the work queue */
destroy_workqueue(di->charger_wq);
/* Unregister external charger enable notifier */
if (!di->ac_chg.enabled)
blocking_notifier_chain_unregister(
&charger_notifier_list, &charger_nb);
flush_scheduled_work();
if (di->usb_chg.enabled)
power_supply_unregister(di->usb_chg.psy);
if (di->ac_chg.enabled && !di->ac_chg.external)
power_supply_unregister(di->ac_chg.psy);
return 0;
}
static char *supply_interface[] = {
"ab8500_chargalg",
"ab8500_fg",
@ -3342,13 +3312,100 @@ static const struct power_supply_desc ab8500_usb_chg_desc = {
.get_property = ab8500_charger_usb_get_property,
};
static int ab8500_charger_bind(struct device *dev)
{
struct ab8500_charger *di = dev_get_drvdata(dev);
int ch_stat;
int ret;
/* Create a work queue for the charger */
di->charger_wq = alloc_ordered_workqueue("ab8500_charger_wq",
WQ_MEM_RECLAIM);
if (di->charger_wq == NULL) {
dev_err(dev, "failed to create work queue\n");
return -ENOMEM;
}
ch_stat = ab8500_charger_detect_chargers(di, false);
if (ch_stat & AC_PW_CONN) {
if (is_ab8500(di->parent))
queue_delayed_work(di->charger_wq,
&di->ac_charger_attached_work,
HZ);
}
if (ch_stat & USB_PW_CONN) {
if (is_ab8500(di->parent))
queue_delayed_work(di->charger_wq,
&di->usb_charger_attached_work,
HZ);
di->vbus_detected = true;
di->vbus_detected_start = true;
queue_work(di->charger_wq,
&di->detect_usb_type_work);
}
ret = component_bind_all(dev, di);
if (ret) {
dev_err(dev, "can't bind component devices\n");
return ret;
}
return 0;
}
static void ab8500_charger_unbind(struct device *dev)
{
struct ab8500_charger *di = dev_get_drvdata(dev);
int ret;
/* Disable AC charging */
ab8500_charger_ac_en(&di->ac_chg, false, 0, 0);
/* Disable USB charging */
ab8500_charger_usb_en(&di->usb_chg, false, 0, 0);
/* Backup battery voltage and current disable */
ret = abx500_mask_and_set_register_interruptible(di->dev,
AB8500_RTC, AB8500_RTC_CTRL_REG, RTC_BUP_CH_ENA, 0);
if (ret < 0)
dev_err(di->dev, "%s mask and set failed\n", __func__);
/* Delete the work queue */
destroy_workqueue(di->charger_wq);
flush_scheduled_work();
/* Unbind fg, btemp, algorithm */
component_unbind_all(dev, di);
}
static const struct component_master_ops ab8500_charger_comp_ops = {
.bind = ab8500_charger_bind,
.unbind = ab8500_charger_unbind,
};
static struct platform_driver *const ab8500_charger_component_drivers[] = {
&ab8500_fg_driver,
&ab8500_btemp_driver,
&abx500_chargalg_driver,
};
static int ab8500_charger_compare_dev(struct device *dev, void *data)
{
return dev == data;
}
static int ab8500_charger_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct component_match *match = NULL;
struct power_supply_config ac_psy_cfg = {}, usb_psy_cfg = {};
struct ab8500_charger *di;
int irq, i, charger_status, ret = 0, ch_stat;
struct device *dev = &pdev->dev;
int charger_status;
int i, irq;
int ret;
di = devm_kzalloc(dev, sizeof(*di), GFP_KERNEL);
if (!di)
@ -3393,6 +3450,38 @@ static int ab8500_charger_probe(struct platform_device *pdev)
return ret;
}
/*
* VDD ADC supply needs to be enabled from this driver when there
* is a charger connected to avoid erroneous BTEMP_HIGH/LOW
* interrupts during charging
*/
di->regu = devm_regulator_get(dev, "vddadc");
if (IS_ERR(di->regu)) {
ret = PTR_ERR(di->regu);
dev_err(dev, "failed to get vddadc regulator\n");
return ret;
}
/* Request interrupts */
for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) {
irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name);
if (irq < 0)
return irq;
ret = devm_request_threaded_irq(dev,
irq, NULL, ab8500_charger_irq[i].isr,
IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT,
ab8500_charger_irq[i].name, di);
if (ret != 0) {
dev_err(dev, "failed to request %s IRQ %d: %d\n"
, ab8500_charger_irq[i].name, irq, ret);
return ret;
}
dev_dbg(dev, "Requested %s IRQ %d: %d\n",
ab8500_charger_irq[i].name, irq, ret);
}
/* initialize lock */
spin_lock_init(&di->usb_state.usb_lock);
mutex_init(&di->usb_ipt_crnt_lock);
@ -3419,14 +3508,16 @@ static int ab8500_charger_probe(struct platform_device *pdev)
di->ac_chg.max_out_curr =
di->bm->chg_output_curr[di->bm->n_chg_out_curr - 1];
di->ac_chg.wdt_refresh = CHG_WD_INTERVAL;
di->ac_chg.enabled = di->bm->ac_enabled;
/*
* The AB8505 only supports USB charging. If we are not the
* AB8505, register an AC charger.
*
* TODO: if this should be opt-in, add DT properties for this.
*/
if (!is_ab8505(di->parent))
di->ac_chg.enabled = true;
di->ac_chg.external = false;
/*notifier for external charger enabling*/
if (!di->ac_chg.enabled)
blocking_notifier_chain_register(
&charger_notifier_list, &charger_nb);
/* USB supply */
/* ux500_charger sub-class */
di->usb_chg.ops.enable = &ab8500_charger_usb_en;
@ -3438,18 +3529,9 @@ static int ab8500_charger_probe(struct platform_device *pdev)
di->usb_chg.max_out_curr =
di->bm->chg_output_curr[di->bm->n_chg_out_curr - 1];
di->usb_chg.wdt_refresh = CHG_WD_INTERVAL;
di->usb_chg.enabled = di->bm->usb_enabled;
di->usb_chg.external = false;
di->usb_state.usb_current = -1;
/* Create a work queue for the charger */
di->charger_wq = alloc_ordered_workqueue("ab8500_charger_wq",
WQ_MEM_RECLAIM);
if (di->charger_wq == NULL) {
dev_err(dev, "failed to create work queue\n");
return -ENOMEM;
}
mutex_init(&di->charger_attached_mutex);
/* Init work for HW failure check */
@ -3500,61 +3582,32 @@ static int ab8500_charger_probe(struct platform_device *pdev)
INIT_WORK(&di->check_usb_thermal_prot_work,
ab8500_charger_check_usb_thermal_prot_work);
/*
* VDD ADC supply needs to be enabled from this driver when there
* is a charger connected to avoid erroneous BTEMP_HIGH/LOW
* interrupts during charging
*/
di->regu = devm_regulator_get(dev, "vddadc");
if (IS_ERR(di->regu)) {
ret = PTR_ERR(di->regu);
dev_err(dev, "failed to get vddadc regulator\n");
goto free_charger_wq;
}
/* Initialize OVV, and other registers */
ret = ab8500_charger_init_hw_registers(di);
if (ret) {
dev_err(dev, "failed to initialize ABB registers\n");
goto free_charger_wq;
return ret;
}
/* Register AC charger class */
if (di->ac_chg.enabled) {
di->ac_chg.psy = power_supply_register(dev,
di->ac_chg.psy = devm_power_supply_register(dev,
&ab8500_ac_chg_desc,
&ac_psy_cfg);
if (IS_ERR(di->ac_chg.psy)) {
dev_err(dev, "failed to register AC charger\n");
ret = PTR_ERR(di->ac_chg.psy);
goto free_charger_wq;
return PTR_ERR(di->ac_chg.psy);
}
}
/* Register USB charger class */
if (di->usb_chg.enabled) {
di->usb_chg.psy = power_supply_register(dev,
&ab8500_usb_chg_desc,
&usb_psy_cfg);
if (IS_ERR(di->usb_chg.psy)) {
dev_err(dev, "failed to register USB charger\n");
ret = PTR_ERR(di->usb_chg.psy);
goto free_ac;
}
}
di->usb_phy = usb_get_phy(USB_PHY_TYPE_USB2);
if (IS_ERR_OR_NULL(di->usb_phy)) {
dev_err(dev, "failed to get usb transceiver\n");
ret = -EINVAL;
goto free_usb;
}
di->nb.notifier_call = ab8500_charger_usb_notifier_call;
ret = usb_register_notifier(di->usb_phy, &di->nb);
if (ret) {
dev_err(dev, "failed to register usb notifier\n");
goto put_usb_phy;
di->usb_chg.psy = devm_power_supply_register(dev,
&ab8500_usb_chg_desc,
&usb_psy_cfg);
if (IS_ERR(di->usb_chg.psy)) {
dev_err(dev, "failed to register USB charger\n");
return PTR_ERR(di->usb_chg.psy);
}
/* Identify the connected charger types during startup */
@ -3566,84 +3619,93 @@ static int ab8500_charger_probe(struct platform_device *pdev)
sysfs_notify(&di->ac_chg.psy->dev.kobj, NULL, "present");
}
if (charger_status & USB_PW_CONN) {
di->vbus_detected = true;
di->vbus_detected_start = true;
queue_work(di->charger_wq,
&di->detect_usb_type_work);
}
/* Register interrupts */
for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) {
irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name);
if (irq < 0) {
ret = irq;
goto free_irq;
}
ret = request_threaded_irq(irq, NULL, ab8500_charger_irq[i].isr,
IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT,
ab8500_charger_irq[i].name, di);
if (ret != 0) {
dev_err(dev, "failed to request %s IRQ %d: %d\n"
, ab8500_charger_irq[i].name, irq, ret);
goto free_irq;
}
dev_dbg(dev, "Requested %s IRQ %d: %d\n",
ab8500_charger_irq[i].name, irq, ret);
}
platform_set_drvdata(pdev, di);
mutex_lock(&di->charger_attached_mutex);
/* Create something that will match the subdrivers when we bind */
for (i = 0; i < ARRAY_SIZE(ab8500_charger_component_drivers); i++) {
struct device_driver *drv = &ab8500_charger_component_drivers[i]->driver;
struct device *p = NULL, *d;
ch_stat = ab8500_charger_detect_chargers(di, false);
if ((ch_stat & AC_PW_CONN) == AC_PW_CONN) {
if (is_ab8500(di->parent))
queue_delayed_work(di->charger_wq,
&di->ac_charger_attached_work,
HZ);
while ((d = platform_find_device_by_driver(p, drv))) {
put_device(p);
component_match_add(dev, &match,
ab8500_charger_compare_dev, d);
p = d;
}
put_device(p);
}
if ((ch_stat & USB_PW_CONN) == USB_PW_CONN) {
if (is_ab8500(di->parent))
queue_delayed_work(di->charger_wq,
&di->usb_charger_attached_work,
HZ);
if (!match) {
dev_err(dev, "no matching components\n");
return -ENODEV;
}
if (IS_ERR(match)) {
dev_err(dev, "could not create component match\n");
return PTR_ERR(match);
}
mutex_unlock(&di->charger_attached_mutex);
/* Notifier for external charger enabling */
if (!di->ac_chg.enabled)
blocking_notifier_chain_register(
&charger_notifier_list, &charger_nb);
return ret;
free_irq:
di->usb_phy = usb_get_phy(USB_PHY_TYPE_USB2);
if (IS_ERR_OR_NULL(di->usb_phy)) {
dev_err(dev, "failed to get usb transceiver\n");
ret = -EINVAL;
goto out_charger_notifier;
}
di->nb.notifier_call = ab8500_charger_usb_notifier_call;
ret = usb_register_notifier(di->usb_phy, &di->nb);
if (ret) {
dev_err(dev, "failed to register usb notifier\n");
goto put_usb_phy;
}
ret = component_master_add_with_match(&pdev->dev,
&ab8500_charger_comp_ops,
match);
if (ret) {
dev_err(dev, "failed to add component master\n");
goto free_notifier;
}
return 0;
free_notifier:
usb_unregister_notifier(di->usb_phy, &di->nb);
/* We also have to free all successfully registered irqs */
for (i = i - 1; i >= 0; i--) {
irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name);
free_irq(irq, di);
}
put_usb_phy:
usb_put_phy(di->usb_phy);
free_usb:
if (di->usb_chg.enabled)
power_supply_unregister(di->usb_chg.psy);
free_ac:
if (di->ac_chg.enabled)
power_supply_unregister(di->ac_chg.psy);
free_charger_wq:
destroy_workqueue(di->charger_wq);
out_charger_notifier:
if (!di->ac_chg.enabled)
blocking_notifier_chain_unregister(
&charger_notifier_list, &charger_nb);
return ret;
}
static int ab8500_charger_remove(struct platform_device *pdev)
{
struct ab8500_charger *di = platform_get_drvdata(pdev);
component_master_del(&pdev->dev, &ab8500_charger_comp_ops);
usb_unregister_notifier(di->usb_phy, &di->nb);
usb_put_phy(di->usb_phy);
if (!di->ac_chg.enabled)
blocking_notifier_chain_unregister(
&charger_notifier_list, &charger_nb);
return 0;
}
static SIMPLE_DEV_PM_OPS(ab8500_charger_pm_ops, ab8500_charger_suspend, ab8500_charger_resume);
static const struct of_device_id ab8500_charger_match[] = {
{ .compatible = "stericsson,ab8500-charger", },
{ },
};
MODULE_DEVICE_TABLE(of, ab8500_charger_match);
static struct platform_driver ab8500_charger_driver = {
.probe = ab8500_charger_probe,
@ -3657,15 +3719,24 @@ static struct platform_driver ab8500_charger_driver = {
static int __init ab8500_charger_init(void)
{
int ret;
ret = platform_register_drivers(ab8500_charger_component_drivers,
ARRAY_SIZE(ab8500_charger_component_drivers));
if (ret)
return ret;
return platform_driver_register(&ab8500_charger_driver);
}
static void __exit ab8500_charger_exit(void)
{
platform_unregister_drivers(ab8500_charger_component_drivers,
ARRAY_SIZE(ab8500_charger_component_drivers));
platform_driver_unregister(&ab8500_charger_driver);
}
subsys_initcall_sync(ab8500_charger_init);
module_init(ab8500_charger_init);
module_exit(ab8500_charger_exit);
MODULE_LICENSE("GPL v2");

View File

@ -17,6 +17,7 @@
#include <linux/init.h>
#include <linux/module.h>
#include <linux/component.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
@ -59,7 +60,7 @@
((y1) + ((((y2) - (y1)) * ((x) - (x1))) / ((x2) - (x1))));
/**
* struct ab8500_fg_interrupts - ab8500 fg interupts
* struct ab8500_fg_interrupts - ab8500 fg interrupts
* @name: name of the interrupt
* @isr function pointer to the isr
*/
@ -2980,27 +2981,6 @@ static int __maybe_unused ab8500_fg_suspend(struct device *dev)
return 0;
}
static int ab8500_fg_remove(struct platform_device *pdev)
{
int ret = 0;
struct ab8500_fg *di = platform_get_drvdata(pdev);
list_del(&di->node);
/* Disable coulomb counter */
ret = ab8500_fg_coulomb_counter(di, false);
if (ret)
dev_err(di->dev, "failed to disable coulomb counter\n");
destroy_workqueue(di->fg_wq);
ab8500_fg_sysfs_exit(di);
flush_scheduled_work();
ab8500_fg_sysfs_psy_remove_attrs(di);
power_supply_unregister(di->fg_psy);
return ret;
}
/* ab8500 fg driver interrupts and their respective isr */
static struct ab8500_fg_interrupts ab8500_fg_irq[] = {
{"NCONV_ACCU", ab8500_fg_cc_convend_handler},
@ -3024,11 +3004,50 @@ static const struct power_supply_desc ab8500_fg_desc = {
.external_power_changed = ab8500_fg_external_power_changed,
};
static int ab8500_fg_bind(struct device *dev, struct device *master,
void *data)
{
struct ab8500_fg *di = dev_get_drvdata(dev);
/* Create a work queue for running the FG algorithm */
di->fg_wq = alloc_ordered_workqueue("ab8500_fg_wq", WQ_MEM_RECLAIM);
if (di->fg_wq == NULL) {
dev_err(dev, "failed to create work queue\n");
return -ENOMEM;
}
/* Start the coulomb counter */
ab8500_fg_coulomb_counter(di, true);
/* Run the FG algorithm */
queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
return 0;
}
static void ab8500_fg_unbind(struct device *dev, struct device *master,
void *data)
{
struct ab8500_fg *di = dev_get_drvdata(dev);
int ret;
/* Disable coulomb counter */
ret = ab8500_fg_coulomb_counter(di, false);
if (ret)
dev_err(dev, "failed to disable coulomb counter\n");
destroy_workqueue(di->fg_wq);
flush_scheduled_work();
}
static const struct component_ops ab8500_fg_component_ops = {
.bind = ab8500_fg_bind,
.unbind = ab8500_fg_unbind,
};
static int ab8500_fg_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct power_supply_config psy_cfg = {};
struct device *dev = &pdev->dev;
struct power_supply_config psy_cfg = {};
struct ab8500_fg *di;
int i, irq;
int ret = 0;
@ -3039,12 +3058,6 @@ static int ab8500_fg_probe(struct platform_device *pdev)
di->bm = &ab8500_bm_data;
ret = ab8500_bm_of_probe(dev, np, di->bm);
if (ret) {
dev_err(dev, "failed to get battery information\n");
return ret;
}
mutex_init(&di->cc_lock);
/* get parent data */
@ -3074,13 +3087,6 @@ static int ab8500_fg_probe(struct platform_device *pdev)
ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT);
ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT);
/* Create a work queue for running the FG algorithm */
di->fg_wq = alloc_ordered_workqueue("ab8500_fg_wq", WQ_MEM_RECLAIM);
if (di->fg_wq == NULL) {
dev_err(dev, "failed to create work queue\n");
return -ENOMEM;
}
/* Init work for running the fg algorithm instantly */
INIT_WORK(&di->fg_work, ab8500_fg_instant_work);
@ -3113,7 +3119,7 @@ static int ab8500_fg_probe(struct platform_device *pdev)
ret = ab8500_fg_init_hw_registers(di);
if (ret) {
dev_err(dev, "failed to initialize registers\n");
goto free_inst_curr_wq;
return ret;
}
/* Consider battery unknown until we're informed otherwise */
@ -3121,15 +3127,13 @@ static int ab8500_fg_probe(struct platform_device *pdev)
di->flags.batt_id_received = false;
/* Register FG power supply class */
di->fg_psy = power_supply_register(dev, &ab8500_fg_desc, &psy_cfg);
di->fg_psy = devm_power_supply_register(dev, &ab8500_fg_desc, &psy_cfg);
if (IS_ERR(di->fg_psy)) {
dev_err(dev, "failed to register FG psy\n");
ret = PTR_ERR(di->fg_psy);
goto free_inst_curr_wq;
return PTR_ERR(di->fg_psy);
}
di->fg_samples = SEC_TO_SAMPLE(di->bm->fg_params->init_timer);
ab8500_fg_coulomb_counter(di, true);
/*
* Initialize completion used to notify completion and start
@ -3141,19 +3145,18 @@ static int ab8500_fg_probe(struct platform_device *pdev)
/* Register primary interrupt handlers */
for (i = 0; i < ARRAY_SIZE(ab8500_fg_irq); i++) {
irq = platform_get_irq_byname(pdev, ab8500_fg_irq[i].name);
if (irq < 0) {
ret = irq;
goto free_irq;
}
if (irq < 0)
return irq;
ret = request_threaded_irq(irq, NULL, ab8500_fg_irq[i].isr,
ret = devm_request_threaded_irq(dev, irq, NULL,
ab8500_fg_irq[i].isr,
IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT,
ab8500_fg_irq[i].name, di);
if (ret != 0) {
dev_err(dev, "failed to request %s IRQ %d: %d\n",
ab8500_fg_irq[i].name, irq, ret);
goto free_irq;
return ret;
}
dev_dbg(dev, "Requested %s IRQ %d: %d\n",
ab8500_fg_irq[i].name, irq, ret);
@ -3168,14 +3171,14 @@ static int ab8500_fg_probe(struct platform_device *pdev)
ret = ab8500_fg_sysfs_init(di);
if (ret) {
dev_err(dev, "failed to create sysfs entry\n");
goto free_irq;
return ret;
}
ret = ab8500_fg_sysfs_psy_create_attrs(di);
if (ret) {
dev_err(dev, "failed to create FG psy\n");
ab8500_fg_sysfs_exit(di);
goto free_irq;
return ret;
}
/* Calibrate the fg first time */
@ -3185,24 +3188,21 @@ static int ab8500_fg_probe(struct platform_device *pdev)
/* Use room temp as default value until we get an update from driver. */
di->bat_temp = 210;
/* Run the FG algorithm */
queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
list_add_tail(&di->node, &ab8500_fg_list);
return ret;
return component_add(dev, &ab8500_fg_component_ops);
}
free_irq:
/* We also have to free all registered irqs */
while (--i >= 0) {
/* Last assignment of i from primary interrupt handlers */
irq = platform_get_irq_byname(pdev, ab8500_fg_irq[i].name);
free_irq(irq, di);
}
static int ab8500_fg_remove(struct platform_device *pdev)
{
int ret = 0;
struct ab8500_fg *di = platform_get_drvdata(pdev);
component_del(&pdev->dev, &ab8500_fg_component_ops);
list_del(&di->node);
ab8500_fg_sysfs_exit(di);
ab8500_fg_sysfs_psy_remove_attrs(di);
power_supply_unregister(di->fg_psy);
free_inst_curr_wq:
destroy_workqueue(di->fg_wq);
return ret;
}
@ -3212,8 +3212,9 @@ static const struct of_device_id ab8500_fg_match[] = {
{ .compatible = "stericsson,ab8500-fg", },
{ },
};
MODULE_DEVICE_TABLE(of, ab8500_fg_match);
static struct platform_driver ab8500_fg_driver = {
struct platform_driver ab8500_fg_driver = {
.probe = ab8500_fg_probe,
.remove = ab8500_fg_remove,
.driver = {
@ -3222,20 +3223,6 @@ static struct platform_driver ab8500_fg_driver = {
.pm = &ab8500_fg_pm_ops,
},
};
static int __init ab8500_fg_init(void)
{
return platform_driver_register(&ab8500_fg_driver);
}
static void __exit ab8500_fg_exit(void)
{
platform_driver_unregister(&ab8500_fg_driver);
}
subsys_initcall_sync(ab8500_fg_init);
module_exit(ab8500_fg_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Johan Palsson, Karl Komierowski");
MODULE_ALIAS("platform:ab8500-fg");

View File

@ -15,6 +15,7 @@
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/component.h>
#include <linux/hrtimer.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
@ -1943,28 +1944,6 @@ static int __maybe_unused abx500_chargalg_suspend(struct device *dev)
return 0;
}
static int abx500_chargalg_remove(struct platform_device *pdev)
{
struct abx500_chargalg *di = platform_get_drvdata(pdev);
/* sysfs interface to enable/disbale charging from user space */
abx500_chargalg_sysfs_exit(di);
hrtimer_cancel(&di->safety_timer);
hrtimer_cancel(&di->maintenance_timer);
cancel_delayed_work_sync(&di->chargalg_periodic_work);
cancel_delayed_work_sync(&di->chargalg_wd_work);
cancel_work_sync(&di->chargalg_work);
/* Delete the work queue */
destroy_workqueue(di->chargalg_wq);
power_supply_unregister(di->chargalg_psy);
return 0;
}
static char *supply_interface[] = {
"ab8500_fg",
};
@ -1978,29 +1957,63 @@ static const struct power_supply_desc abx500_chargalg_desc = {
.external_power_changed = abx500_chargalg_external_power_changed,
};
static int abx500_chargalg_bind(struct device *dev, struct device *master,
void *data)
{
struct abx500_chargalg *di = dev_get_drvdata(dev);
/* Create a work queue for the chargalg */
di->chargalg_wq = alloc_ordered_workqueue("abx500_chargalg_wq",
WQ_MEM_RECLAIM);
if (di->chargalg_wq == NULL) {
dev_err(di->dev, "failed to create work queue\n");
return -ENOMEM;
}
/* Run the charging algorithm */
queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0);
return 0;
}
static void abx500_chargalg_unbind(struct device *dev, struct device *master,
void *data)
{
struct abx500_chargalg *di = dev_get_drvdata(dev);
/* Stop all timers and work */
hrtimer_cancel(&di->safety_timer);
hrtimer_cancel(&di->maintenance_timer);
cancel_delayed_work_sync(&di->chargalg_periodic_work);
cancel_delayed_work_sync(&di->chargalg_wd_work);
cancel_work_sync(&di->chargalg_work);
/* Delete the work queue */
destroy_workqueue(di->chargalg_wq);
flush_scheduled_work();
}
static const struct component_ops abx500_chargalg_component_ops = {
.bind = abx500_chargalg_bind,
.unbind = abx500_chargalg_unbind,
};
static int abx500_chargalg_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device *dev = &pdev->dev;
struct power_supply_config psy_cfg = {};
struct abx500_chargalg *di;
int ret = 0;
di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL);
if (!di) {
dev_err(&pdev->dev, "%s no mem for ab8500_chargalg\n", __func__);
di = devm_kzalloc(dev, sizeof(*di), GFP_KERNEL);
if (!di)
return -ENOMEM;
}
di->bm = &ab8500_bm_data;
ret = ab8500_bm_of_probe(&pdev->dev, np, di->bm);
if (ret) {
dev_err(&pdev->dev, "failed to get battery information\n");
return ret;
}
/* get device struct and parent */
di->dev = &pdev->dev;
di->dev = dev;
di->parent = dev_get_drvdata(pdev->dev.parent);
psy_cfg.supplied_to = supply_interface;
@ -2016,14 +2029,6 @@ static int abx500_chargalg_probe(struct platform_device *pdev)
di->maintenance_timer.function =
abx500_chargalg_maintenance_timer_expired;
/* Create a work queue for the chargalg */
di->chargalg_wq = alloc_ordered_workqueue("abx500_chargalg_wq",
WQ_MEM_RECLAIM);
if (di->chargalg_wq == NULL) {
dev_err(di->dev, "failed to create work queue\n");
return -ENOMEM;
}
/* Init work for chargalg */
INIT_DEFERRABLE_WORK(&di->chargalg_periodic_work,
abx500_chargalg_periodic_work);
@ -2037,12 +2042,12 @@ static int abx500_chargalg_probe(struct platform_device *pdev)
di->chg_info.prev_conn_chg = -1;
/* Register chargalg power supply class */
di->chargalg_psy = power_supply_register(di->dev, &abx500_chargalg_desc,
di->chargalg_psy = devm_power_supply_register(di->dev,
&abx500_chargalg_desc,
&psy_cfg);
if (IS_ERR(di->chargalg_psy)) {
dev_err(di->dev, "failed to register chargalg psy\n");
ret = PTR_ERR(di->chargalg_psy);
goto free_chargalg_wq;
return PTR_ERR(di->chargalg_psy);
}
platform_set_drvdata(pdev, di);
@ -2051,21 +2056,24 @@ static int abx500_chargalg_probe(struct platform_device *pdev)
ret = abx500_chargalg_sysfs_init(di);
if (ret) {
dev_err(di->dev, "failed to create sysfs entry\n");
goto free_psy;
return ret;
}
di->curr_status.curr_step = CHARGALG_CURR_STEP_HIGH;
/* Run the charging algorithm */
queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0);
dev_info(di->dev, "probe success\n");
return ret;
return component_add(dev, &abx500_chargalg_component_ops);
}
free_psy:
power_supply_unregister(di->chargalg_psy);
free_chargalg_wq:
destroy_workqueue(di->chargalg_wq);
return ret;
static int abx500_chargalg_remove(struct platform_device *pdev)
{
struct abx500_chargalg *di = platform_get_drvdata(pdev);
component_del(&pdev->dev, &abx500_chargalg_component_ops);
/* sysfs interface to enable/disable charging from user space */
abx500_chargalg_sysfs_exit(di);
return 0;
}
static SIMPLE_DEV_PM_OPS(abx500_chargalg_pm_ops, abx500_chargalg_suspend, abx500_chargalg_resume);
@ -2075,7 +2083,7 @@ static const struct of_device_id ab8500_chargalg_match[] = {
{ },
};
static struct platform_driver abx500_chargalg_driver = {
struct platform_driver abx500_chargalg_driver = {
.probe = abx500_chargalg_probe,
.remove = abx500_chargalg_remove,
.driver = {
@ -2084,9 +2092,6 @@ static struct platform_driver abx500_chargalg_driver = {
.pm = &abx500_chargalg_pm_ops,
},
};
module_platform_driver(abx500_chargalg_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Johan Palsson, Karl Komierowski");
MODULE_ALIAS("platform:abx500-chargalg");

View File

@ -40,6 +40,7 @@
#define AXP209_FG_PERCENT GENMASK(6, 0)
#define AXP22X_FG_VALID BIT(7)
#define AXP20X_CHRG_CTRL1_ENABLE BIT(7)
#define AXP20X_CHRG_CTRL1_TGT_VOLT GENMASK(6, 5)
#define AXP20X_CHRG_CTRL1_TGT_4_1V (0 << 5)
#define AXP20X_CHRG_CTRL1_TGT_4_15V (1 << 5)
@ -468,7 +469,18 @@ static int axp20x_battery_set_prop(struct power_supply *psy,
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
return axp20x_set_max_constant_charge_current(axp20x_batt,
val->intval);
case POWER_SUPPLY_PROP_STATUS:
switch (val->intval) {
case POWER_SUPPLY_STATUS_CHARGING:
return regmap_update_bits(axp20x_batt->regmap, AXP20X_CHRG_CTRL1,
AXP20X_CHRG_CTRL1_ENABLE, AXP20X_CHRG_CTRL1_ENABLE);
case POWER_SUPPLY_STATUS_DISCHARGING:
case POWER_SUPPLY_STATUS_NOT_CHARGING:
return regmap_update_bits(axp20x_batt->regmap, AXP20X_CHRG_CTRL1,
AXP20X_CHRG_CTRL1_ENABLE, 0);
}
fallthrough;
default:
return -EINVAL;
}
@ -491,7 +503,8 @@ static enum power_supply_property axp20x_battery_props[] = {
static int axp20x_battery_prop_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN ||
return psp == POWER_SUPPLY_PROP_STATUS ||
psp == POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN ||
psp == POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN ||
psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT ||
psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX;

View File

@ -142,9 +142,7 @@ static int fuel_gauge_reg_readb(struct axp288_fg_info *info, int reg)
for (i = 0; i < NR_RETRY_CNT; i++) {
ret = regmap_read(info->regmap, reg, &val);
if (ret == -EBUSY)
continue;
else
if (ret != -EBUSY)
break;
}
@ -676,7 +674,7 @@ intr_failed:
* detection reports one despite it not being there.
* Please keep this listed sorted alphabetically.
*/
static const struct dmi_system_id axp288_fuel_gauge_blacklist[] = {
static const struct dmi_system_id axp288_no_battery_list[] = {
{
/* ACEPC T8 Cherry Trail Z8350 mini PC */
.matches = {
@ -723,15 +721,6 @@ static const struct dmi_system_id axp288_fuel_gauge_blacklist[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "MEEGOPAD T02"),
},
},
{
/* Meegopad T08 */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Default string"),
DMI_MATCH(DMI_BOARD_VENDOR, "To be filled by OEM."),
DMI_MATCH(DMI_BOARD_NAME, "T3 MRD"),
DMI_MATCH(DMI_BOARD_VERSION, "V1.1"),
},
},
{ /* Mele PCG03 Mini PC */
.matches = {
DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Mini PC"),
@ -745,6 +734,15 @@ static const struct dmi_system_id axp288_fuel_gauge_blacklist[] = {
DMI_MATCH(DMI_PRODUCT_NAME, "Z83-4"),
}
},
{
/* Various Ace PC/Meegopad/MinisForum/Wintel Mini-PCs/HDMI-sticks */
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "T3 MRD"),
DMI_MATCH(DMI_CHASSIS_TYPE, "3"),
DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."),
DMI_MATCH(DMI_BIOS_VERSION, "5.11"),
},
},
{}
};
@ -764,7 +762,7 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev)
};
unsigned int val;
if (dmi_check_system(axp288_fuel_gauge_blacklist))
if (dmi_check_system(axp288_no_battery_list))
return -ENODEV;
/*

View File

@ -1,710 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
//
// Copyright (C) 2018 ROHM Semiconductors
//
// power-supply driver for ROHM BD70528 PMIC
/*
* BD70528 charger HW state machine.
*
* The thermal shutdown state is not drawn. From any other state but
* battery error and suspend it is possible to go to TSD/TMP states
* if temperature is out of bounds.
*
* CHG_RST = H
* or CHG_EN=L
* or (DCIN2_UVLO=L && DCIN1_UVLO=L)
* or (DCIN2_OVLO=H & DCIN1_UVKLO=L)
*
* +--------------+ +--------------+
* | | | |
* | Any state +-------> | Suspend |
* | | | |
* +--------------+ +------+-------+
* |
* CHG_EN = H && BAT_DET = H && |
* No errors (temp, bat_ov, UVLO, |
* OVLO...) |
* |
* BAT_OV or +---------v----------+
* (DBAT && TTRI) | |
* +-----------------+ Trickle Charge | <---------------+
* | | | |
* | +-------+------------+ |
* | | |
* | | ^ |
* | V_BAT > VTRI_TH | | VBAT < VTRI_TH - 50mV |
* | | | |
* | v | |
* | | |
* | BAT_OV or +----------+----+ |
* | (DBAT && TFST) | | |
* | +----------------+ Fast Charge | |
* | | | | |
* v v +----+----------+ |
* | |
*+----------------+ ILIM_DET=L | ^ ILIM_DET |
*| | & CV_DET=H | | or CV_DET=L |
*| Battery Error | & VBAT > | | or VBAT < VRECHG_TH |
*| | VRECHG_TH | | or IBAT > IFST/x |
*+----------------+ & IBAT < | | |
* IFST/x v | |
* ^ | |
* | +---------+-+ |
* | | | |
* +-------------------+ Top OFF | |
* BAT_OV = H or | | |
* (DBAT && TFST) +-----+-----+ |
* | |
* Stay top-off for 15s | |
* v |
* |
* +--------+ |
* | | |
* | Done +-------------------------+
* | |
* +--------+ VBAT < VRECHG_TH
*/
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/mfd/rohm-bd70528.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/linear_range.h>
#define CHG_STAT_SUSPEND 0x0
#define CHG_STAT_TRICKLE 0x1
#define CHG_STAT_FAST 0x3
#define CHG_STAT_TOPOFF 0xe
#define CHG_STAT_DONE 0xf
#define CHG_STAT_OTP_TRICKLE 0x10
#define CHG_STAT_OTP_FAST 0x11
#define CHG_STAT_OTP_DONE 0x12
#define CHG_STAT_TSD_TRICKLE 0x20
#define CHG_STAT_TSD_FAST 0x21
#define CHG_STAT_TSD_TOPOFF 0x22
#define CHG_STAT_BAT_ERR 0x7f
static const char *bd70528_charger_model = "BD70528";
static const char *bd70528_charger_manufacturer = "ROHM Semiconductors";
#define BD_ERR_IRQ_HND(_name_, _wrn_) \
static irqreturn_t bd0528_##_name_##_interrupt(int irq, void *arg) \
{ \
struct power_supply *psy = (struct power_supply *)arg; \
\
power_supply_changed(psy); \
dev_err(&psy->dev, (_wrn_)); \
\
return IRQ_HANDLED; \
}
#define BD_INFO_IRQ_HND(_name_, _wrn_) \
static irqreturn_t bd0528_##_name_##_interrupt(int irq, void *arg) \
{ \
struct power_supply *psy = (struct power_supply *)arg; \
\
power_supply_changed(psy); \
dev_dbg(&psy->dev, (_wrn_)); \
\
return IRQ_HANDLED; \
}
#define BD_IRQ_HND(_name_) bd0528_##_name_##_interrupt
struct bd70528_psy {
struct regmap *regmap;
struct device *dev;
struct power_supply *psy;
};
BD_ERR_IRQ_HND(BAT_OV_DET, "Battery overvoltage detected\n");
BD_ERR_IRQ_HND(DBAT_DET, "Dead battery detected\n");
BD_ERR_IRQ_HND(COLD_DET, "Battery cold\n");
BD_ERR_IRQ_HND(HOT_DET, "Battery hot\n");
BD_ERR_IRQ_HND(CHG_TSD, "Charger thermal shutdown\n");
BD_ERR_IRQ_HND(DCIN2_OV_DET, "DCIN2 overvoltage detected\n");
BD_INFO_IRQ_HND(BAT_OV_RES, "Battery voltage back to normal\n");
BD_INFO_IRQ_HND(COLD_RES, "Battery temperature back to normal\n");
BD_INFO_IRQ_HND(HOT_RES, "Battery temperature back to normal\n");
BD_INFO_IRQ_HND(BAT_RMV, "Battery removed\n");
BD_INFO_IRQ_HND(BAT_DET, "Battery detected\n");
BD_INFO_IRQ_HND(DCIN2_OV_RES, "DCIN2 voltage back to normal\n");
BD_INFO_IRQ_HND(DCIN2_RMV, "DCIN2 removed\n");
BD_INFO_IRQ_HND(DCIN2_DET, "DCIN2 detected\n");
BD_INFO_IRQ_HND(DCIN1_RMV, "DCIN1 removed\n");
BD_INFO_IRQ_HND(DCIN1_DET, "DCIN1 detected\n");
struct irq_name_pair {
const char *n;
irqreturn_t (*h)(int irq, void *arg);
};
static int bd70528_get_irqs(struct platform_device *pdev,
struct bd70528_psy *bdpsy)
{
int irq, i, ret;
unsigned int mask;
static const struct irq_name_pair bd70528_chg_irqs[] = {
{ .n = "bd70528-bat-ov-res", .h = BD_IRQ_HND(BAT_OV_RES) },
{ .n = "bd70528-bat-ov-det", .h = BD_IRQ_HND(BAT_OV_DET) },
{ .n = "bd70528-bat-dead", .h = BD_IRQ_HND(DBAT_DET) },
{ .n = "bd70528-bat-warmed", .h = BD_IRQ_HND(COLD_RES) },
{ .n = "bd70528-bat-cold", .h = BD_IRQ_HND(COLD_DET) },
{ .n = "bd70528-bat-cooled", .h = BD_IRQ_HND(HOT_RES) },
{ .n = "bd70528-bat-hot", .h = BD_IRQ_HND(HOT_DET) },
{ .n = "bd70528-chg-tshd", .h = BD_IRQ_HND(CHG_TSD) },
{ .n = "bd70528-bat-removed", .h = BD_IRQ_HND(BAT_RMV) },
{ .n = "bd70528-bat-detected", .h = BD_IRQ_HND(BAT_DET) },
{ .n = "bd70528-dcin2-ov-res", .h = BD_IRQ_HND(DCIN2_OV_RES) },
{ .n = "bd70528-dcin2-ov-det", .h = BD_IRQ_HND(DCIN2_OV_DET) },
{ .n = "bd70528-dcin2-removed", .h = BD_IRQ_HND(DCIN2_RMV) },
{ .n = "bd70528-dcin2-detected", .h = BD_IRQ_HND(DCIN2_DET) },
{ .n = "bd70528-dcin1-removed", .h = BD_IRQ_HND(DCIN1_RMV) },
{ .n = "bd70528-dcin1-detected", .h = BD_IRQ_HND(DCIN1_DET) },
};
for (i = 0; i < ARRAY_SIZE(bd70528_chg_irqs); i++) {
irq = platform_get_irq_byname(pdev, bd70528_chg_irqs[i].n);
if (irq < 0) {
dev_err(&pdev->dev, "Bad IRQ information for %s (%d)\n",
bd70528_chg_irqs[i].n, irq);
return irq;
}
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
bd70528_chg_irqs[i].h,
IRQF_ONESHOT,
bd70528_chg_irqs[i].n,
bdpsy->psy);
if (ret)
return ret;
}
/*
* BD70528 irq controller is not touching the main mask register.
* So enable the charger block interrupts at main level. We can just
* leave them enabled as irq-controller should disable irqs
* from sub-registers when IRQ is disabled or freed.
*/
mask = BD70528_REG_INT_BAT1_MASK | BD70528_REG_INT_BAT2_MASK;
ret = regmap_update_bits(bdpsy->regmap,
BD70528_REG_INT_MAIN_MASK, mask, 0);
if (ret)
dev_err(&pdev->dev, "Failed to enable charger IRQs\n");
return ret;
}
static int bd70528_get_charger_status(struct bd70528_psy *bdpsy, int *val)
{
int ret;
unsigned int v;
ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_CURR_STAT, &v);
if (ret) {
dev_err(bdpsy->dev, "Charger state read failure %d\n",
ret);
return ret;
}
switch (v & BD70528_MASK_CHG_STAT) {
case CHG_STAT_SUSPEND:
/* Maybe we should check the CHG_TTRI_EN? */
case CHG_STAT_OTP_TRICKLE:
case CHG_STAT_OTP_FAST:
case CHG_STAT_OTP_DONE:
case CHG_STAT_TSD_TRICKLE:
case CHG_STAT_TSD_FAST:
case CHG_STAT_TSD_TOPOFF:
case CHG_STAT_BAT_ERR:
*val = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
case CHG_STAT_DONE:
*val = POWER_SUPPLY_STATUS_FULL;
break;
case CHG_STAT_TRICKLE:
case CHG_STAT_FAST:
case CHG_STAT_TOPOFF:
*val = POWER_SUPPLY_STATUS_CHARGING;
break;
default:
*val = POWER_SUPPLY_STATUS_UNKNOWN;
break;
}
return 0;
}
static int bd70528_get_charge_type(struct bd70528_psy *bdpsy, int *val)
{
int ret;
unsigned int v;
ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_CURR_STAT, &v);
if (ret) {
dev_err(bdpsy->dev, "Charger state read failure %d\n",
ret);
return ret;
}
switch (v & BD70528_MASK_CHG_STAT) {
case CHG_STAT_TRICKLE:
*val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
break;
case CHG_STAT_FAST:
case CHG_STAT_TOPOFF:
*val = POWER_SUPPLY_CHARGE_TYPE_FAST;
break;
case CHG_STAT_DONE:
case CHG_STAT_SUSPEND:
/* Maybe we should check the CHG_TTRI_EN? */
case CHG_STAT_OTP_TRICKLE:
case CHG_STAT_OTP_FAST:
case CHG_STAT_OTP_DONE:
case CHG_STAT_TSD_TRICKLE:
case CHG_STAT_TSD_FAST:
case CHG_STAT_TSD_TOPOFF:
case CHG_STAT_BAT_ERR:
*val = POWER_SUPPLY_CHARGE_TYPE_NONE;
break;
default:
*val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
break;
}
return 0;
}
static int bd70528_get_battery_health(struct bd70528_psy *bdpsy, int *val)
{
int ret;
unsigned int v;
ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_BAT_STAT, &v);
if (ret) {
dev_err(bdpsy->dev, "Battery state read failure %d\n",
ret);
return ret;
}
/* No battery? */
if (!(v & BD70528_MASK_CHG_BAT_DETECT))
*val = POWER_SUPPLY_HEALTH_DEAD;
else if (v & BD70528_MASK_CHG_BAT_OVERVOLT)
*val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
else if (v & BD70528_MASK_CHG_BAT_TIMER)
*val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
else
*val = POWER_SUPPLY_HEALTH_GOOD;
return 0;
}
static int bd70528_get_online(struct bd70528_psy *bdpsy, int *val)
{
int ret;
unsigned int v;
ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_IN_STAT, &v);
if (ret) {
dev_err(bdpsy->dev, "DC1 IN state read failure %d\n",
ret);
return ret;
}
*val = (v & BD70528_MASK_CHG_DCIN1_UVLO) ? 1 : 0;
return 0;
}
static int bd70528_get_present(struct bd70528_psy *bdpsy, int *val)
{
int ret;
unsigned int v;
ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_BAT_STAT, &v);
if (ret) {
dev_err(bdpsy->dev, "Battery state read failure %d\n",
ret);
return ret;
}
*val = (v & BD70528_MASK_CHG_BAT_DETECT) ? 1 : 0;
return 0;
}
static const struct linear_range current_limit_ranges[] = {
{
.min = 5,
.step = 1,
.min_sel = 0,
.max_sel = 0x22,
},
{
.min = 40,
.step = 5,
.min_sel = 0x23,
.max_sel = 0x26,
},
{
.min = 60,
.step = 20,
.min_sel = 0x27,
.max_sel = 0x2d,
},
{
.min = 200,
.step = 50,
.min_sel = 0x2e,
.max_sel = 0x34,
},
{
.min = 500,
.step = 0,
.min_sel = 0x35,
.max_sel = 0x3f,
},
};
/*
* BD70528 would support setting and getting own charge current/
* voltage for low temperatures. The driver currently only reads
* the charge current at room temperature. We do set both though.
*/
static const struct linear_range warm_charge_curr[] = {
{
.min = 10,
.step = 10,
.min_sel = 0,
.max_sel = 0x12,
},
{
.min = 200,
.step = 25,
.min_sel = 0x13,
.max_sel = 0x1f,
},
};
/*
* Cold charge current selectors are identical to warm charge current
* selectors. The difference is that only smaller currents are available
* at cold charge range.
*/
#define MAX_COLD_CHG_CURR_SEL 0x15
#define MAX_WARM_CHG_CURR_SEL 0x1f
#define MIN_CHG_CURR_SEL 0x0
static int get_charge_current(struct bd70528_psy *bdpsy, int *ma)
{
unsigned int sel;
int ret;
ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_CHG_CURR_WARM,
&sel);
if (ret) {
dev_err(bdpsy->dev,
"Charge current reading failed (%d)\n", ret);
return ret;
}
sel &= BD70528_MASK_CHG_CHG_CURR;
ret = linear_range_get_value_array(&warm_charge_curr[0],
ARRAY_SIZE(warm_charge_curr),
sel, ma);
if (ret) {
dev_err(bdpsy->dev,
"Unknown charge current value 0x%x\n",
sel);
}
return ret;
}
static int get_current_limit(struct bd70528_psy *bdpsy, int *ma)
{
unsigned int sel;
int ret;
ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_DCIN_ILIM,
&sel);
if (ret) {
dev_err(bdpsy->dev,
"Input current limit reading failed (%d)\n", ret);
return ret;
}
sel &= BD70528_MASK_CHG_DCIN_ILIM;
ret = linear_range_get_value_array(&current_limit_ranges[0],
ARRAY_SIZE(current_limit_ranges),
sel, ma);
if (ret) {
/* Unspecified values mean 500 mA */
*ma = 500;
}
return 0;
}
static enum power_supply_property bd70528_charger_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
};
static int bd70528_charger_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct bd70528_psy *bdpsy = power_supply_get_drvdata(psy);
int ret = 0;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
return bd70528_get_charger_status(bdpsy, &val->intval);
case POWER_SUPPLY_PROP_CHARGE_TYPE:
return bd70528_get_charge_type(bdpsy, &val->intval);
case POWER_SUPPLY_PROP_HEALTH:
return bd70528_get_battery_health(bdpsy, &val->intval);
case POWER_SUPPLY_PROP_PRESENT:
return bd70528_get_present(bdpsy, &val->intval);
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
ret = get_current_limit(bdpsy, &val->intval);
val->intval *= 1000;
return ret;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
ret = get_charge_current(bdpsy, &val->intval);
val->intval *= 1000;
return ret;
case POWER_SUPPLY_PROP_ONLINE:
return bd70528_get_online(bdpsy, &val->intval);
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = bd70528_charger_model;
return 0;
case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = bd70528_charger_manufacturer;
return 0;
default:
break;
}
return -EINVAL;
}
static int bd70528_prop_is_writable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
return 1;
default:
break;
}
return 0;
}
static int set_charge_current(struct bd70528_psy *bdpsy, int ma)
{
unsigned int reg;
int ret = 0, tmpret;
bool found;
if (ma > 500) {
dev_warn(bdpsy->dev,
"Requested charge current %u exceed maximum (500mA)\n",
ma);
reg = MAX_WARM_CHG_CURR_SEL;
goto set;
}
if (ma < 10) {
dev_err(bdpsy->dev,
"Requested charge current %u smaller than min (10mA)\n",
ma);
reg = MIN_CHG_CURR_SEL;
ret = -EINVAL;
goto set;
}
/*
* For BD70528 voltage/current limits we happily accept any value which
* belongs the range. We could check if value matching the selector is
* desired by computing the range min + (sel - sel_low) * range step - but
* I guess it is enough if we use voltage/current which is closest (below)
* the requested?
*/
ret = linear_range_get_selector_low_array(warm_charge_curr,
ARRAY_SIZE(warm_charge_curr),
ma, &reg, &found);
if (ret) {
dev_err(bdpsy->dev,
"Unsupported charge current %u mA\n", ma);
reg = MIN_CHG_CURR_SEL;
goto set;
}
if (!found) {
/*
* There was a gap in supported values and we hit it.
* Yet a smaller value was found so we use it.
*/
dev_warn(bdpsy->dev,
"Unsupported charge current %u mA\n", ma);
}
set:
tmpret = regmap_update_bits(bdpsy->regmap,
BD70528_REG_CHG_CHG_CURR_WARM,
BD70528_MASK_CHG_CHG_CURR, reg);
if (tmpret)
dev_err(bdpsy->dev,
"Charge current write failure (%d)\n", tmpret);
if (reg > MAX_COLD_CHG_CURR_SEL)
reg = MAX_COLD_CHG_CURR_SEL;
if (!tmpret)
tmpret = regmap_update_bits(bdpsy->regmap,
BD70528_REG_CHG_CHG_CURR_COLD,
BD70528_MASK_CHG_CHG_CURR, reg);
if (!ret)
ret = tmpret;
return ret;
}
#define MAX_CURR_LIMIT_SEL 0x34
#define MIN_CURR_LIMIT_SEL 0x0
static int set_current_limit(struct bd70528_psy *bdpsy, int ma)
{
unsigned int reg;
int ret = 0, tmpret;
bool found;
if (ma > 500) {
dev_warn(bdpsy->dev,
"Requested current limit %u exceed maximum (500mA)\n",
ma);
reg = MAX_CURR_LIMIT_SEL;
goto set;
}
if (ma < 5) {
dev_err(bdpsy->dev,
"Requested current limit %u smaller than min (5mA)\n",
ma);
reg = MIN_CURR_LIMIT_SEL;
ret = -EINVAL;
goto set;
}
ret = linear_range_get_selector_low_array(current_limit_ranges,
ARRAY_SIZE(current_limit_ranges),
ma, &reg, &found);
if (ret) {
dev_err(bdpsy->dev, "Unsupported current limit %umA\n", ma);
reg = MIN_CURR_LIMIT_SEL;
goto set;
}
if (!found) {
/*
* There was a gap in supported values and we hit it.
* We found a smaller value from ranges and use it.
* Warn user though.
*/
dev_warn(bdpsy->dev, "Unsupported current limit %umA\n", ma);
}
set:
tmpret = regmap_update_bits(bdpsy->regmap,
BD70528_REG_CHG_DCIN_ILIM,
BD70528_MASK_CHG_DCIN_ILIM, reg);
if (!ret)
ret = tmpret;
return ret;
}
static int bd70528_charger_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct bd70528_psy *bdpsy = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
return set_current_limit(bdpsy, val->intval / 1000);
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
return set_charge_current(bdpsy, val->intval / 1000);
default:
break;
}
return -EINVAL;
}
static const struct power_supply_desc bd70528_charger_desc = {
.name = "bd70528-charger",
.type = POWER_SUPPLY_TYPE_MAINS,
.properties = bd70528_charger_props,
.num_properties = ARRAY_SIZE(bd70528_charger_props),
.get_property = bd70528_charger_get_property,
.set_property = bd70528_charger_set_property,
.property_is_writeable = bd70528_prop_is_writable,
};
static int bd70528_power_probe(struct platform_device *pdev)
{
struct bd70528_psy *bdpsy;
struct power_supply_config cfg = {};
bdpsy = devm_kzalloc(&pdev->dev, sizeof(*bdpsy), GFP_KERNEL);
if (!bdpsy)
return -ENOMEM;
bdpsy->regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!bdpsy->regmap) {
dev_err(&pdev->dev, "No regmap found for chip\n");
return -EINVAL;
}
bdpsy->dev = &pdev->dev;
platform_set_drvdata(pdev, bdpsy);
cfg.drv_data = bdpsy;
cfg.of_node = pdev->dev.parent->of_node;
bdpsy->psy = devm_power_supply_register(&pdev->dev,
&bd70528_charger_desc, &cfg);
if (IS_ERR(bdpsy->psy)) {
dev_err(&pdev->dev, "failed: power supply register\n");
return PTR_ERR(bdpsy->psy);
}
return bd70528_get_irqs(pdev, bdpsy);
}
static struct platform_driver bd70528_power = {
.driver = {
.name = "bd70528-power"
},
.probe = bd70528_power_probe,
};
module_platform_driver(bd70528_power);
MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>");
MODULE_DESCRIPTION("BD70528 power-supply driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:bd70528-power");

View File

@ -5,11 +5,10 @@
* Author: Mark A. Greer <mgreer@animalcreek.com>
*/
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/of_irq.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <linux/power_supply.h>
#include <linux/power/bq24190_charger.h>
@ -1959,7 +1958,6 @@ static const struct i2c_device_id bq24190_i2c_ids[] = {
};
MODULE_DEVICE_TABLE(i2c, bq24190_i2c_ids);
#ifdef CONFIG_OF
static const struct of_device_id bq24190_of_match[] = {
{ .compatible = "ti,bq24190", },
{ .compatible = "ti,bq24192", },
@ -1968,11 +1966,6 @@ static const struct of_device_id bq24190_of_match[] = {
{ },
};
MODULE_DEVICE_TABLE(of, bq24190_of_match);
#else
static const struct of_device_id bq24190_of_match[] = {
{ },
};
#endif
static struct i2c_driver bq24190_driver = {
.probe = bq24190_probe,
@ -1981,7 +1974,7 @@ static struct i2c_driver bq24190_driver = {
.driver = {
.name = "bq24190-charger",
.pm = &bq24190_pm_ops,
.of_match_table = of_match_ptr(bq24190_of_match),
.of_match_table = bq24190_of_match,
},
};
module_i2c_driver(bq24190_driver);

View File

@ -1279,6 +1279,7 @@ static const struct of_device_id charger_manager_match[] = {
},
{},
};
MODULE_DEVICE_TABLE(of, charger_manager_match);
static struct charger_desc *of_cm_parse_desc(struct device *dev)
{

View File

@ -667,10 +667,23 @@ static int cpcap_battery_get_property(struct power_supply *psy,
if (!empty->voltage)
return -ENODATA;
val->intval = empty->counter_uah - latest->counter_uah;
if (val->intval < 0)
if (val->intval < 0) {
/* Assume invalid config if CHARGE_NOW is -20% */
if (ddata->charge_full && abs(val->intval) > ddata->charge_full/5) {
empty->voltage = 0;
ddata->charge_full = 0;
return -ENODATA;
}
val->intval = 0;
else if (ddata->charge_full && ddata->charge_full < val->intval)
} else if (ddata->charge_full && ddata->charge_full < val->intval) {
/* Assume invalid config if CHARGE_NOW exceeds CHARGE_FULL by 20% */
if (val->intval > (6*ddata->charge_full)/5) {
empty->voltage = 0;
ddata->charge_full = 0;
return -ENODATA;
}
val->intval = ddata->charge_full;
}
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
if (!ddata->charge_full)
@ -747,7 +760,7 @@ static int cpcap_battery_set_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_CHARGE_FULL:
if (val->intval < 0)
return -EINVAL;
if (val->intval > ddata->config.info.charge_full_design)
if (val->intval > (6*ddata->config.info.charge_full_design)/5)
return -EINVAL;
ddata->charge_full = val->intval;

View File

@ -173,23 +173,6 @@ static enum power_supply_property cpcap_charger_props[] = {
POWER_SUPPLY_PROP_CURRENT_NOW,
};
/* No battery always shows temperature of -40000 */
static bool cpcap_charger_battery_found(struct cpcap_charger_ddata *ddata)
{
struct iio_channel *channel;
int error, temperature;
channel = ddata->channels[CPCAP_CHARGER_IIO_BATTDET];
error = iio_read_channel_processed(channel, &temperature);
if (error < 0) {
dev_warn(ddata->dev, "%s failed: %i\n", __func__, error);
return false;
}
return temperature > -20000 && temperature < 60000;
}
static int cpcap_charger_get_charge_voltage(struct cpcap_charger_ddata *ddata)
{
struct iio_channel *channel;
@ -700,11 +683,29 @@ static void cpcap_usb_detect(struct work_struct *work)
if (!ddata->feeding_vbus && cpcap_charger_vbus_valid(ddata) &&
s.chrgcurr1) {
int max_current = 532000;
int max_current;
int vchrg, ichrg;
union power_supply_propval val;
struct power_supply *battery;
if (cpcap_charger_battery_found(ddata))
battery = power_supply_get_by_name("battery");
if (IS_ERR_OR_NULL(battery)) {
dev_err(ddata->dev, "battery power_supply not available %li\n",
PTR_ERR(battery));
return;
}
error = power_supply_get_property(battery, POWER_SUPPLY_PROP_PRESENT, &val);
power_supply_put(battery);
if (error)
goto out_err;
if (val.intval) {
max_current = 1596000;
} else {
dev_info(ddata->dev, "battery not inserted, charging disabled\n");
max_current = 0;
}
if (max_current > ddata->limit_current)
max_current = ddata->limit_current;

View File

@ -16,7 +16,6 @@
#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>
@ -142,13 +141,10 @@ struct max17040_chip {
struct regmap *regmap;
struct delayed_work work;
struct power_supply *battery;
struct max17040_platform_data *pdata;
struct chip_data data;
/* 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 */
@ -221,26 +217,7 @@ static int max17040_get_version(struct max17040_chip *chip)
static int max17040_get_online(struct max17040_chip *chip)
{
return chip->pdata && chip->pdata->battery_online ?
chip->pdata->battery_online() : 1;
}
static int max17040_get_status(struct max17040_chip *chip)
{
if (!chip->pdata || !chip->pdata->charger_online
|| !chip->pdata->charger_enable)
return POWER_SUPPLY_STATUS_UNKNOWN;
if (max17040_get_soc(chip) > MAX17040_BATTERY_FULL)
return POWER_SUPPLY_STATUS_FULL;
if (chip->pdata->charger_online())
if (chip->pdata->charger_enable())
return POWER_SUPPLY_STATUS_CHARGING;
else
return POWER_SUPPLY_STATUS_NOT_CHARGING;
else
return POWER_SUPPLY_STATUS_DISCHARGING;
return 1;
}
static int max17040_get_of_data(struct max17040_chip *chip)
@ -283,7 +260,6 @@ static int max17040_get_of_data(struct max17040_chip *chip)
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)
@ -302,17 +278,16 @@ static void max17040_stop_work(void *data)
static void max17040_work(struct work_struct *work)
{
struct max17040_chip *chip;
int last_soc, last_status;
int last_soc;
chip = container_of(work, struct max17040_chip, work.work);
/* store SOC and status to check changes */
/* store SOC to check changes */
last_soc = chip->soc;
last_status = chip->status;
max17040_check_changes(chip);
/* check changes and send uevent */
if (last_soc != chip->soc || last_status != chip->status)
if (last_soc != chip->soc)
power_supply_changed(chip->battery);
max17040_queue_work(chip);
@ -361,12 +336,10 @@ static irqreturn_t max17040_thread_handler(int id, void *dev)
static int max17040_enable_alert_irq(struct max17040_chip *chip)
{
struct i2c_client *client = chip->client;
unsigned int flags;
int ret;
flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
max17040_thread_handler, flags,
max17040_thread_handler, IRQF_ONESHOT,
chip->battery->desc->name, chip);
return ret;
@ -415,9 +388,6 @@ static int max17040_get_property(struct power_supply *psy,
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;
@ -444,7 +414,6 @@ static const struct regmap_config max17040_regmap = {
};
static enum power_supply_property max17040_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CAPACITY,
@ -480,7 +449,6 @@ static int max17040_probe(struct i2c_client *client,
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);

View File

@ -1104,7 +1104,7 @@ static int max17042_probe(struct i2c_client *client,
}
if (client->irq) {
unsigned int flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
unsigned int flags = IRQF_ONESHOT;
/*
* On ACPI systems the IRQ may be handled by ACPI-event code,

File diff suppressed because it is too large Load Diff

View File

@ -37,8 +37,27 @@
#define CHG_STATE_NO_BAT2 13
#define CHG_STATE_CHG_READY_VUSB 14
#define GCHGDET_TYPE_MASK 0x30
#define GCHGDET_TYPE_SDP 0x00
#define GCHGDET_TYPE_CDP 0x10
#define GCHGDET_TYPE_DCP 0x20
#define FG_ENABLE 1
/*
* Formula seems accurate for battery current, but for USB current around 70mA
* per step was seen on Kobo Clara HD but all sources show the same formula
* also fur USB current. To avoid accidentially unwanted high currents we stick
* to that formula
*/
#define TO_CUR_REG(x) ((x) / 100000 - 1)
#define FROM_CUR_REG(x) ((((x) & 0x1f) + 1) * 100000)
#define CHG_MIN_CUR 100000
#define CHG_MAX_CUR 1800000
#define ADP_MAX_CUR 2500000
#define USB_MAX_CUR 1400000
struct rn5t618_power_info {
struct rn5t618 *rn5t618;
struct platform_device *pdev;
@ -48,12 +67,24 @@ struct rn5t618_power_info {
int irq;
};
static enum power_supply_usb_type rn5t618_usb_types[] = {
POWER_SUPPLY_USB_TYPE_SDP,
POWER_SUPPLY_USB_TYPE_DCP,
POWER_SUPPLY_USB_TYPE_CDP,
POWER_SUPPLY_USB_TYPE_UNKNOWN
};
static enum power_supply_property rn5t618_usb_props[] = {
/* input current limit is not very accurate */
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_USB_TYPE,
POWER_SUPPLY_PROP_ONLINE,
};
static enum power_supply_property rn5t618_adp_props[] = {
/* input current limit is not very accurate */
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
};
@ -69,6 +100,7 @@ static enum power_supply_property rn5t618_battery_props[] = {
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW,
};
@ -258,6 +290,36 @@ static int rn5t618_battery_ttf(struct rn5t618_power_info *info,
return 0;
}
static int rn5t618_battery_set_current_limit(struct rn5t618_power_info *info,
const union power_supply_propval *val)
{
if (val->intval < CHG_MIN_CUR)
return -EINVAL;
if (val->intval >= CHG_MAX_CUR)
return -EINVAL;
return regmap_update_bits(info->rn5t618->regmap,
RN5T618_CHGISET,
0x1F, TO_CUR_REG(val->intval));
}
static int rn5t618_battery_get_current_limit(struct rn5t618_power_info *info,
union power_supply_propval *val)
{
unsigned int regval;
int ret;
ret = regmap_read(info->rn5t618->regmap, RN5T618_CHGISET,
&regval);
if (ret < 0)
return ret;
val->intval = FROM_CUR_REG(regval);
return 0;
}
static int rn5t618_battery_charge_full(struct rn5t618_power_info *info,
union power_supply_propval *val)
{
@ -323,6 +385,9 @@ static int rn5t618_battery_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
break;
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
ret = rn5t618_battery_get_current_limit(info, val);
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
ret = rn5t618_battery_charge_full(info, val);
break;
@ -336,12 +401,38 @@ static int rn5t618_battery_get_property(struct power_supply *psy,
return ret;
}
static int rn5t618_battery_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct rn5t618_power_info *info = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
return rn5t618_battery_set_current_limit(info, val);
default:
return -EINVAL;
}
}
static int rn5t618_battery_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
return true;
default:
return false;
}
}
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;
unsigned int regval;
bool online;
int ret;
@ -364,6 +455,14 @@ static int rn5t618_adp_get_property(struct power_supply *psy,
if (val->intval != POWER_SUPPLY_STATUS_CHARGING)
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
ret = regmap_read(info->rn5t618->regmap,
RN5T618_REGISET1, &regval);
if (ret < 0)
return ret;
val->intval = FROM_CUR_REG(regval);
break;
default:
return -EINVAL;
@ -372,12 +471,79 @@ static int rn5t618_adp_get_property(struct power_supply *psy,
return 0;
}
static int rn5t618_adp_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct rn5t618_power_info *info = power_supply_get_drvdata(psy);
int ret;
switch (psp) {
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
if (val->intval > ADP_MAX_CUR)
return -EINVAL;
if (val->intval < CHG_MIN_CUR)
return -EINVAL;
ret = regmap_write(info->rn5t618->regmap, RN5T618_REGISET1,
TO_CUR_REG(val->intval));
if (ret < 0)
return ret;
break;
default:
return -EINVAL;
}
return 0;
}
static int rn5t618_adp_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
return true;
default:
return false;
}
}
static int rc5t619_usb_get_type(struct rn5t618_power_info *info,
union power_supply_propval *val)
{
unsigned int regval;
int ret;
ret = regmap_read(info->rn5t618->regmap, RN5T618_GCHGDET, &regval);
if (ret < 0)
return ret;
switch (regval & GCHGDET_TYPE_MASK) {
case GCHGDET_TYPE_SDP:
val->intval = POWER_SUPPLY_USB_TYPE_SDP;
break;
case GCHGDET_TYPE_CDP:
val->intval = POWER_SUPPLY_USB_TYPE_CDP;
break;
case GCHGDET_TYPE_DCP:
val->intval = POWER_SUPPLY_USB_TYPE_DCP;
break;
default:
val->intval = POWER_SUPPLY_USB_TYPE_UNKNOWN;
}
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;
unsigned int regval;
bool online;
int ret;
@ -400,6 +566,28 @@ static int rn5t618_usb_get_property(struct power_supply *psy,
if (val->intval != POWER_SUPPLY_STATUS_CHARGING)
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
case POWER_SUPPLY_PROP_USB_TYPE:
if (!online || (info->rn5t618->variant != RC5T619))
return -ENODATA;
return rc5t619_usb_get_type(info, val);
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
ret = regmap_read(info->rn5t618->regmap, RN5T618_CHGCTL1,
&regval);
if (ret < 0)
return ret;
val->intval = 0;
if (regval & 2) {
ret = regmap_read(info->rn5t618->regmap,
RN5T618_REGISET2,
&regval);
if (ret < 0)
return ret;
val->intval = FROM_CUR_REG(regval);
}
break;
default:
return -EINVAL;
@ -408,12 +596,53 @@ static int rn5t618_usb_get_property(struct power_supply *psy,
return 0;
}
static int rn5t618_usb_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct rn5t618_power_info *info = power_supply_get_drvdata(psy);
int ret;
switch (psp) {
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
if (val->intval > USB_MAX_CUR)
return -EINVAL;
if (val->intval < CHG_MIN_CUR)
return -EINVAL;
ret = regmap_write(info->rn5t618->regmap, RN5T618_REGISET2,
0xE0 | TO_CUR_REG(val->intval));
if (ret < 0)
return ret;
break;
default:
return -EINVAL;
}
return 0;
}
static int rn5t618_usb_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
return true;
default:
return false;
}
}
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,
.set_property = rn5t618_battery_set_property,
.property_is_writeable = rn5t618_battery_property_is_writeable,
};
static const struct power_supply_desc rn5t618_adp_desc = {
@ -422,14 +651,20 @@ static const struct power_supply_desc rn5t618_adp_desc = {
.properties = rn5t618_adp_props,
.num_properties = ARRAY_SIZE(rn5t618_adp_props),
.get_property = rn5t618_adp_get_property,
.set_property = rn5t618_adp_set_property,
.property_is_writeable = rn5t618_adp_property_is_writeable,
};
static const struct power_supply_desc rn5t618_usb_desc = {
.name = "rn5t618-usb",
.type = POWER_SUPPLY_TYPE_USB,
.usb_types = rn5t618_usb_types,
.num_usb_types = ARRAY_SIZE(rn5t618_usb_types),
.properties = rn5t618_usb_props,
.num_properties = ARRAY_SIZE(rn5t618_usb_props),
.get_property = rn5t618_usb_get_property,
.set_property = rn5t618_usb_set_property,
.property_is_writeable = rn5t618_usb_property_is_writeable,
};
static irqreturn_t rn5t618_charger_irq(int irq, void *data)

View File

@ -164,9 +164,16 @@ static const struct i2c_device_id rt5033_battery_id[] = {
};
MODULE_DEVICE_TABLE(i2c, rt5033_battery_id);
static const struct of_device_id rt5033_battery_of_match[] = {
{ .compatible = "richtek,rt5033-battery", },
{ }
};
MODULE_DEVICE_TABLE(of, rt5033_battery_of_match);
static struct i2c_driver rt5033_battery_driver = {
.driver = {
.name = "rt5033-battery",
.of_match_table = rt5033_battery_of_match,
},
.probe = rt5033_battery_probe,
.remove = rt5033_battery_remove,

View File

@ -189,6 +189,14 @@ static const enum power_supply_property sbs_properties[] = {
/* Supports special manufacturer commands from TI BQ20Z65 and BQ20Z75 IC. */
#define SBS_FLAGS_TI_BQ20ZX5 BIT(0)
static const enum power_supply_property string_properties[] = {
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_MODEL_NAME,
};
#define NR_STRING_BUFFERS ARRAY_SIZE(string_properties)
struct sbs_info {
struct i2c_client *client;
struct power_supply *power_supply;
@ -202,11 +210,32 @@ struct sbs_info {
struct delayed_work work;
struct mutex mode_lock;
u32 flags;
int technology;
char strings[NR_STRING_BUFFERS][I2C_SMBUS_BLOCK_MAX + 1];
};
static char model_name[I2C_SMBUS_BLOCK_MAX + 1];
static char manufacturer[I2C_SMBUS_BLOCK_MAX + 1];
static char chemistry[I2C_SMBUS_BLOCK_MAX + 1];
static char *sbs_get_string_buf(struct sbs_info *chip,
enum power_supply_property psp)
{
int i = 0;
for (i = 0; i < NR_STRING_BUFFERS; i++)
if (string_properties[i] == psp)
return chip->strings[i];
return ERR_PTR(-EINVAL);
}
static void sbs_invalidate_cached_props(struct sbs_info *chip)
{
int i = 0;
chip->technology = -1;
for (i = 0; i < NR_STRING_BUFFERS; i++)
chip->strings[i][0] = 0;
}
static bool force_load;
static int sbs_read_word_data(struct i2c_client *client, u8 address);
@ -244,6 +273,7 @@ static int sbs_update_presence(struct sbs_info *chip, bool is_present)
chip->is_present = false;
/* Disable PEC when no device is present */
client->flags &= ~I2C_CLIENT_PEC;
sbs_invalidate_cached_props(chip);
return 0;
}
@ -640,17 +670,45 @@ static int sbs_get_battery_property(struct i2c_client *client,
return 0;
}
static int sbs_get_battery_string_property(struct i2c_client *client,
int reg_offset, enum power_supply_property psp, char *val)
static int sbs_get_property_index(struct i2c_client *client,
enum power_supply_property psp)
{
s32 ret;
int count;
ret = sbs_read_string_data(client, sbs_data[reg_offset].addr, val);
for (count = 0; count < ARRAY_SIZE(sbs_data); count++)
if (psp == sbs_data[count].psp)
return count;
if (ret < 0)
return ret;
dev_warn(&client->dev,
"%s: Invalid Property - %d\n", __func__, psp);
return 0;
return -EINVAL;
}
static const char *sbs_get_constant_string(struct sbs_info *chip,
enum power_supply_property psp)
{
int ret;
char *buf;
u8 addr;
buf = sbs_get_string_buf(chip, psp);
if (IS_ERR(buf))
return buf;
if (!buf[0]) {
ret = sbs_get_property_index(chip->client, psp);
if (ret < 0)
return ERR_PTR(ret);
addr = sbs_data[ret].addr;
ret = sbs_read_string_data(chip->client, addr, buf);
if (ret < 0)
return ERR_PTR(ret);
}
return buf;
}
static void sbs_unit_adjustment(struct i2c_client *client,
@ -773,48 +831,36 @@ static int sbs_get_battery_serial_number(struct i2c_client *client,
return 0;
}
static int sbs_get_property_index(struct i2c_client *client,
enum power_supply_property psp)
{
int count;
for (count = 0; count < ARRAY_SIZE(sbs_data); count++)
if (psp == sbs_data[count].psp)
return count;
dev_warn(&client->dev,
"%s: Invalid Property - %d\n", __func__, psp);
return -EINVAL;
}
static int sbs_get_chemistry(struct i2c_client *client,
static int sbs_get_chemistry(struct sbs_info *chip,
union power_supply_propval *val)
{
enum power_supply_property psp = POWER_SUPPLY_PROP_TECHNOLOGY;
int ret;
const char *chemistry;
ret = sbs_get_property_index(client, psp);
if (ret < 0)
return ret;
if (chip->technology != -1) {
val->intval = chip->technology;
return 0;
}
ret = sbs_get_battery_string_property(client, ret, psp,
chemistry);
if (ret < 0)
return ret;
chemistry = sbs_get_constant_string(chip, POWER_SUPPLY_PROP_TECHNOLOGY);
if (IS_ERR(chemistry))
return PTR_ERR(chemistry);
if (!strncasecmp(chemistry, "LION", 4))
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
chip->technology = POWER_SUPPLY_TECHNOLOGY_LION;
else if (!strncasecmp(chemistry, "LiP", 3))
val->intval = POWER_SUPPLY_TECHNOLOGY_LIPO;
chip->technology = POWER_SUPPLY_TECHNOLOGY_LIPO;
else if (!strncasecmp(chemistry, "NiCd", 4))
val->intval = POWER_SUPPLY_TECHNOLOGY_NiCd;
chip->technology = POWER_SUPPLY_TECHNOLOGY_NiCd;
else if (!strncasecmp(chemistry, "NiMH", 4))
val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH;
chip->technology = POWER_SUPPLY_TECHNOLOGY_NiMH;
else
val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
chip->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
if (val->intval == POWER_SUPPLY_TECHNOLOGY_UNKNOWN)
dev_warn(&client->dev, "Unknown chemistry: %s\n", chemistry);
if (chip->technology == POWER_SUPPLY_TECHNOLOGY_UNKNOWN)
dev_warn(&chip->client->dev, "Unknown chemistry: %s\n", chemistry);
val->intval = chip->technology;
return 0;
}
@ -858,6 +904,7 @@ static int sbs_get_property(struct power_supply *psy,
int ret = 0;
struct sbs_info *chip = power_supply_get_drvdata(psy);
struct i2c_client *client = chip->client;
const char *str;
if (chip->gpio_detect) {
ret = gpiod_get_value_cansleep(chip->gpio_detect);
@ -883,7 +930,7 @@ static int sbs_get_property(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
ret = sbs_get_chemistry(client, val);
ret = sbs_get_chemistry(chip, val);
if (ret < 0)
break;
@ -935,23 +982,12 @@ static int sbs_get_property(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
ret = sbs_get_property_index(client, psp);
if (ret < 0)
break;
ret = sbs_get_battery_string_property(client, ret, psp,
model_name);
val->strval = model_name;
break;
case POWER_SUPPLY_PROP_MANUFACTURER:
ret = sbs_get_property_index(client, psp);
if (ret < 0)
break;
ret = sbs_get_battery_string_property(client, ret, psp,
manufacturer);
val->strval = manufacturer;
str = sbs_get_constant_string(chip, psp);
if (IS_ERR(str))
ret = PTR_ERR(str);
else
val->strval = str;
break;
case POWER_SUPPLY_PROP_MANUFACTURE_YEAR:
@ -1098,6 +1134,7 @@ static int sbs_probe(struct i2c_client *client)
psy_cfg.of_node = client->dev.of_node;
psy_cfg.drv_data = chip;
chip->last_state = POWER_SUPPLY_STATUS_UNKNOWN;
sbs_invalidate_cached_props(chip);
mutex_init(&chip->mode_lock);
/* use pdata if available, fall back to DT properties,

View File

@ -524,6 +524,7 @@ static const struct of_device_id sc2731_charger_of_match[] = {
{ .compatible = "sprd,sc2731-charger", },
{ }
};
MODULE_DEVICE_TABLE(of, sc2731_charger_of_match);
static struct platform_driver sc2731_charger_driver = {
.driver = {

View File

@ -1342,6 +1342,7 @@ static const struct of_device_id sc27xx_fgu_of_match[] = {
{ .compatible = "sprd,sc2731-fgu", },
{ }
};
MODULE_DEVICE_TABLE(of, sc27xx_fgu_of_match);
static struct platform_driver sc27xx_fgu_driver = {
.probe = sc27xx_fgu_probe,

View File

@ -10,7 +10,6 @@
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>

View File

@ -345,6 +345,16 @@ static u32 spwr_notify_bat(struct ssam_event_notifier *nf, const struct ssam_eve
struct spwr_battery_device *bat = container_of(nf, struct spwr_battery_device, notif);
int status;
/*
* We cannot use strict matching when registering the notifier as the
* EC expects us to register it against instance ID 0. Strict matching
* would thus drop events, as those may have non-zero instance IDs in
* this subsystem. So we need to check the instance ID of the event
* here manually.
*/
if (event->instance_id != bat->sdev->uid.instance)
return 0;
dev_dbg(&bat->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n",
event->command_id, event->instance_id, event->target_id);
@ -720,8 +730,8 @@ static void spwr_battery_init(struct spwr_battery_device *bat, struct ssam_devic
bat->notif.base.fn = spwr_notify_bat;
bat->notif.event.reg = registry;
bat->notif.event.id.target_category = sdev->uid.category;
bat->notif.event.id.instance = 0;
bat->notif.event.mask = SSAM_EVENT_MASK_STRICT;
bat->notif.event.id.instance = 0; /* need to register with instance 0 */
bat->notif.event.mask = SSAM_EVENT_MASK_TARGET;
bat->notif.event.flags = SSAM_EVENT_SEQUENCED;
bat->psy_desc.name = bat->name;

View File

@ -66,7 +66,7 @@ struct spwr_ac_device {
static int spwr_ac_update_unlocked(struct spwr_ac_device *ac)
{
u32 old = ac->state;
__le32 old = ac->state;
int status;
lockdep_assert_held(&ac->lock);

View File

@ -1,16 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2009 Samsung Electronics
* Minkyu Kang <mk7.kang@samsung.com>
*/
#ifndef __MAX17040_BATTERY_H_
#define __MAX17040_BATTERY_H_
struct max17040_platform_data {
int (*battery_online)(void);
int (*charger_online)(void);
int (*charger_enable)(void);
};
#endif

View File

@ -1,48 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* PM2301 charger driver.
*
* Copyright (C) 2012 ST Ericsson Corporation
*
* Contact: Olivier LAUNAY (olivier.launay@stericsson.com
*/
#ifndef __LINUX_PM2301_H
#define __LINUX_PM2301_H
/**
* struct pm2xxx_bm_charger_parameters - Charger specific parameters
* @ac_volt_max: maximum allowed AC charger voltage in mV
* @ac_curr_max: maximum allowed AC charger current in mA
*/
struct pm2xxx_bm_charger_parameters {
int ac_volt_max;
int ac_curr_max;
};
/**
* struct pm2xxx_bm_data - pm2xxx battery management data
* @enable_overshoot flag to enable VBAT overshoot control
* @chg_params charger parameters
*/
struct pm2xxx_bm_data {
bool enable_overshoot;
const struct pm2xxx_bm_charger_parameters *chg_params;
};
struct pm2xxx_charger_platform_data {
char **supplied_to;
size_t num_supplicants;
int i2c_bus;
const char *label;
int gpio_irq_number;
unsigned int lpn_gpio;
int irq_type;
};
struct pm2xxx_platform_data {
struct pm2xxx_charger_platform_data *wall_charger;
struct pm2xxx_bm_data *battery;
};
#endif /* __LINUX_PM2301_H */

View File

@ -1,16 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) ST-Ericsson 2013
* Author: Hongbo Zhang <hongbo.zhang@linaro.com>
*/
#ifndef PWR_AB8500_H
#define PWR_AB8500_H
extern const struct abx500_res_to_temp ab8500_temp_tbl_a_thermistor[];
extern const int ab8500_temp_tbl_a_size;
extern const struct abx500_res_to_temp ab8500_temp_tbl_b_thermistor[];
extern const int ab8500_temp_tbl_b_size;
#endif /* PWR_AB8500_H */