mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-24 04:34:08 +08:00
power supply and reset changes for the v4.2 series
* New charger drivers: BQ24257, BQ25890, AXP288, RT9455 * MAX17042 battery: add health & temperature support * BQ2415x charger: add ACPI support * misc. fixes and cleanups -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABCgAGBQJViI++AAoJENju1/PIO/qasPYP/0YMZdNNEUA/z1Uv8litaGbe gCzLRKuevt/996eVQcGJwsla/7AZwNgpUJya28mpiRqp+RbsTB4bkiGCkD2aO2Jw p34h9tIugU5H8+iL1+vl/ztpjwwm/bxEQHNpHK/YwqQLP7QazI6/yyRAUzYu0bGk RUndaStY/UZ/9KtSfGYF54kpxCXVQS0aHPMXXKQREr3Hg/VMlBoKQeAvJ7jTTjST 7s9ZncO70jeY1NrSSdRY+anbuUNYqt0ndbaHhlpayGxnWL+PGdd2mNKq/ycGmBld 8PwnDs+6fLv24PGYnkwbTryCbdvU9ZYUuNVb4XNTY+8x8CSPNuM8yYhqkmmq1dfI zXy1U9TCkLtKSk+7rxKWZc0WuWn2D9UJvr1zAoK9TMafdLi3YIMbm1TemKpLX/HJ chORnioPNgBiLmSzJ+nizWfWU5BQ5MhJamzhER0dRG9u8/2YJzCyNfd2miGrvAdL LecLcWopEDa+cAg5HM3usKcv0GsBJIes/jmtSMv2URWk8FHcXsDMsnl+D/77/LmV PM+HNJ/cNi7WGMgMO6lb1gULaRdAb6tZS865p2Abx5NkNjjRQw9A8EIwP30SPWh7 aPhU6jxDdmhpez4nJeyHdjcQqnlE+Cxz9vwj8IMgneTjSl4guUY1HcgmAHJtrKGC WNB/r32UzJ5AZ0VxBB15 =192M -----END PGP SIGNATURE----- Merge tag 'for-4.2' of git://git.infradead.org/battery-2.6 Pull power supply and reset updates from Sebastian Reichel: - new charger drivers: BQ24257, BQ25890, AXP288, RT9455 - MAX17042 battery: add health & temperature support - BQ2415x charger: add ACPI support - misc fixes and cleanups * tag 'for-4.2' of git://git.infradead.org/battery-2.6: (32 commits) power_supply: Correct kerneldoc copy paste errors wm831x_power: Fix off-by-one at free_irq() power_supply: rt9455_charger: Fix error reported by static analysis tool power_supply: bq24257: use flags argument of devm_gpiod_get power_supply: bq25890: use flags argument of devm_gpiod_get sbs-battery: add option to always register battery power: Add devm_power_supply_get_by_phandle() helper function power_supply: max17042: Add OF support for setting thresholds power_supply: sysfs: Bring back write to writeable properties power_supply: rt9455_charger: Check if CONFIG_USB_PHY is enabled power: reset: gpio-restart: increase priority slightly power_supply: bq25890: make chip_id int power_supply: Add support for Richtek RT9455 battery charger Documentation: devicetree: Add Richtek RT9455 bindings of: Add vendor prefix for Richtek Technology Corporation power_supply: 88pm860x_charger: Do not call free_irq() twice power: bq24190_charger: Change first_time flag reset condition power: axp288_charger: axp288 charger driver power: max17042_battery: add HEALTH and TEMP_* properties support power_supply: Add support for TI BQ25890 charger chip ...
This commit is contained in:
commit
36a1624d88
21
Documentation/devicetree/bindings/power/bq24257.txt
Normal file
21
Documentation/devicetree/bindings/power/bq24257.txt
Normal file
@ -0,0 +1,21 @@
|
||||
Binding for TI bq24257 Li-Ion Charger
|
||||
|
||||
Required properties:
|
||||
- compatible: Should contain one of the following:
|
||||
* "ti,bq24257"
|
||||
- reg: integer, i2c address of the device.
|
||||
- ti,battery-regulation-voltage: integer, maximum charging voltage in uV.
|
||||
- ti,charge-current: integer, maximum charging current in uA.
|
||||
- ti,termination-current: integer, charge will be terminated when current in
|
||||
constant-voltage phase drops below this value (in uA).
|
||||
|
||||
Example:
|
||||
|
||||
bq24257 {
|
||||
compatible = "ti,bq24257";
|
||||
reg = <0x6a>;
|
||||
|
||||
ti,battery-regulation-voltage = <4200000>;
|
||||
ti,charge-current = <1000000>;
|
||||
ti,termination-current = <50000>;
|
||||
};
|
46
Documentation/devicetree/bindings/power/bq25890.txt
Normal file
46
Documentation/devicetree/bindings/power/bq25890.txt
Normal file
@ -0,0 +1,46 @@
|
||||
Binding for TI bq25890 Li-Ion Charger
|
||||
|
||||
Required properties:
|
||||
- compatible: Should contain one of the following:
|
||||
* "ti,bq25890"
|
||||
- reg: integer, i2c address of the device.
|
||||
- ti,battery-regulation-voltage: integer, maximum charging voltage (in uV);
|
||||
- ti,charge-current: integer, maximum charging current (in uA);
|
||||
- ti,termination-current: integer, charge will be terminated when current in
|
||||
constant-voltage phase drops below this value (in uA);
|
||||
- ti,precharge-current: integer, maximum charge current during precharge
|
||||
phase (in uA);
|
||||
- ti,minimum-sys-voltage: integer, when battery is charging and it is below
|
||||
minimum system voltage, the system will be regulated above
|
||||
minimum-sys-voltage setting (in uV);
|
||||
- ti,boost-voltage: integer, VBUS voltage level in boost mode (in uV);
|
||||
- ti,boost-max-current: integer, maximum allowed current draw in boost mode
|
||||
(in uA).
|
||||
|
||||
Optional properties:
|
||||
- ti,boost-low-freq: boolean, if present boost mode frequency will be 500kHz,
|
||||
otherwise 1.5MHz;
|
||||
- ti,use-ilim-pin: boolean, if present the ILIM resistor will be used and the
|
||||
input current will be the lower between the resistor setting and the IINLIM
|
||||
register setting;
|
||||
- ti,thermal-regulation-threshold: integer, temperature above which the charge
|
||||
current is lowered, to avoid overheating (in degrees Celsius). If omitted,
|
||||
the default setting will be used (120 degrees);
|
||||
|
||||
Example:
|
||||
|
||||
bq25890 {
|
||||
compatible = "ti,bq25890";
|
||||
reg = <0x6a>;
|
||||
|
||||
ti,battery-regulation-voltage = <4200000>;
|
||||
ti,charge-current = <1000000>;
|
||||
ti,termination-current = <50000>;
|
||||
ti,precharge-current = <128000>;
|
||||
ti,minimum-sys-voltage = <3600000>;
|
||||
ti,boost-voltage = <5000000>;
|
||||
ti,boost-max-current = <1000000>;
|
||||
|
||||
ti,use-ilim-pin;
|
||||
ti,thermal-regulation-threshold = <120>;
|
||||
};
|
48
Documentation/devicetree/bindings/power/rt9455_charger.txt
Normal file
48
Documentation/devicetree/bindings/power/rt9455_charger.txt
Normal file
@ -0,0 +1,48 @@
|
||||
Binding for Richtek rt9455 battery charger
|
||||
|
||||
Required properties:
|
||||
- compatible: it should contain one of the following:
|
||||
"richtek,rt9455".
|
||||
- reg: integer, i2c address of the device.
|
||||
- interrupt-parent: the phandle for the interrupt controller that
|
||||
services interrupts for this device.
|
||||
- interrupts: interrupt mapping for GPIO IRQ, it should be
|
||||
configured with IRQ_TYPE_LEVEL_LOW flag.
|
||||
- richtek,output-charge-current: integer, output current from the charger to the
|
||||
battery, in uA.
|
||||
- richtek,end-of-charge-percentage: integer, percent of the output charge current.
|
||||
When the current in constant-voltage phase drops
|
||||
below output_charge_current x end-of-charge-percentage,
|
||||
charge is terminated.
|
||||
- richtek,battery-regulation-voltage: integer, maximum battery voltage in uV.
|
||||
- richtek,boost-output-voltage: integer, maximum voltage provided to consumer
|
||||
devices, when the charger is in boost mode, in uV.
|
||||
|
||||
Optional properties:
|
||||
- richtek,min-input-voltage-regulation: integer, input voltage level in uV, used to
|
||||
decrease voltage level when the over current
|
||||
of the input power source occurs.
|
||||
This prevents input voltage drop due to insufficient
|
||||
current provided by the power source.
|
||||
Default: 4500000 uV (4.5V)
|
||||
- richtek,avg-input-current-regulation: integer, input current value in uA drained by the
|
||||
charger from the power source.
|
||||
Default: 500000 uA (500mA)
|
||||
|
||||
Example:
|
||||
|
||||
rt9455@22 {
|
||||
compatible = "richtek,rt9455";
|
||||
reg = <0x22>;
|
||||
|
||||
interrupt-parent = <&gpio1>;
|
||||
interrupts = <0 IRQ_TYPE_LEVEL_LOW>;
|
||||
|
||||
richtek,output-charge-current = <500000>;
|
||||
richtek,end-of-charge-percentage = <10>;
|
||||
richtek,battery-regulation-voltage = <4200000>;
|
||||
richtek,boost-output-voltage = <5050000>;
|
||||
|
||||
richtek,min-input-voltage-regulation = <4500000>;
|
||||
richtek,avg-input-current-regulation = <500000>;
|
||||
};
|
@ -9,10 +9,23 @@ Optional properties :
|
||||
(datasheet-recommended value is 10000).
|
||||
Defining this property enables current-sense functionality.
|
||||
|
||||
Optional threshold properties :
|
||||
If skipped the condition won't be reported.
|
||||
- maxim,cold-temp : Temperature threshold to report battery
|
||||
as cold (in tenths of degree Celsius).
|
||||
- maxim,over-heat-temp : Temperature threshold to report battery
|
||||
as over heated (in tenths of degree Celsius).
|
||||
- maxim,dead-volt : Voltage threshold to report battery
|
||||
as dead (in mV).
|
||||
- maxim,over-volt : Voltage threshold to report battery
|
||||
as over voltage (in mV).
|
||||
|
||||
Example:
|
||||
|
||||
battery-charger@36 {
|
||||
compatible = "maxim,max17042";
|
||||
reg = <0x36>;
|
||||
maxim,rsns-microohm = <10000>;
|
||||
maxim,over-heat-temp = <600>;
|
||||
maxim,over-volt = <4300>;
|
||||
};
|
||||
|
@ -161,6 +161,7 @@ ralink Mediatek/Ralink Technology Corp.
|
||||
ramtron Ramtron International
|
||||
realtek Realtek Semiconductor Corp.
|
||||
renesas Renesas Electronics Corporation
|
||||
richtek Richtek Technology Corporation
|
||||
ricoh Ricoh Co. Ltd.
|
||||
rockchip Fuzhou Rockchip Electronics Co., Ltd
|
||||
samsung Samsung Semiconductor
|
||||
|
@ -742,7 +742,6 @@ static int pm860x_charger_remove(struct platform_device *pdev)
|
||||
int i;
|
||||
|
||||
power_supply_unregister(info->usb);
|
||||
free_irq(info->irq[0], info);
|
||||
for (i = 0; i < info->irq_nums; i++)
|
||||
free_irq(info->irq[i], info);
|
||||
return 0;
|
||||
|
@ -204,6 +204,13 @@ config CHARGER_DA9150
|
||||
This driver can also be built as a module. If so, the module will be
|
||||
called da9150-charger.
|
||||
|
||||
config AXP288_CHARGER
|
||||
tristate "X-Powers AXP288 Charger"
|
||||
depends on MFD_AXP20X && EXTCON_AXP288
|
||||
help
|
||||
Say yes here to have support X-Power AXP288 power management IC (PMIC)
|
||||
integrated charger.
|
||||
|
||||
config AXP288_FUEL_GAUGE
|
||||
tristate "X-Powers AXP288 Fuel Gauge"
|
||||
depends on MFD_AXP20X && IIO
|
||||
@ -388,12 +395,26 @@ config CHARGER_BQ24190
|
||||
help
|
||||
Say Y to enable support for the TI BQ24190 battery charger.
|
||||
|
||||
config CHARGER_BQ24257
|
||||
tristate "TI BQ24257 battery charger driver"
|
||||
depends on I2C && GPIOLIB
|
||||
depends on REGMAP_I2C
|
||||
help
|
||||
Say Y to enable support for the TI BQ24257 battery charger.
|
||||
|
||||
config CHARGER_BQ24735
|
||||
tristate "TI BQ24735 battery charger support"
|
||||
depends on I2C && GPIOLIB
|
||||
help
|
||||
Say Y to enable support for the TI BQ24735 battery charger.
|
||||
|
||||
config CHARGER_BQ25890
|
||||
tristate "TI BQ25890 battery charger driver"
|
||||
depends on I2C && GPIOLIB
|
||||
select REGMAP_I2C
|
||||
help
|
||||
Say Y to enable support for the TI BQ25890 battery charger.
|
||||
|
||||
config CHARGER_SMB347
|
||||
tristate "Summit Microelectronics SMB347 Battery Charger"
|
||||
depends on I2C
|
||||
@ -439,6 +460,13 @@ config BATTERY_RT5033
|
||||
The fuelgauge calculates and determines the battery state of charge
|
||||
according to battery open circuit voltage.
|
||||
|
||||
config CHARGER_RT9455
|
||||
tristate "Richtek RT9455 battery charger driver"
|
||||
depends on I2C && GPIOLIB
|
||||
select REGMAP_I2C
|
||||
help
|
||||
Say Y to enable support for Richtek RT9455 battery charger.
|
||||
|
||||
source "drivers/power/reset/Kconfig"
|
||||
|
||||
endif # POWER_SUPPLY
|
||||
|
@ -37,6 +37,7 @@ obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o
|
||||
obj-$(CONFIG_BATTERY_MAX17042) += max17042_battery.o
|
||||
obj-$(CONFIG_BATTERY_Z2) += z2_battery.o
|
||||
obj-$(CONFIG_BATTERY_RT5033) += rt5033_battery.o
|
||||
obj-$(CONFIG_CHARGER_RT9455) += rt9455_charger.o
|
||||
obj-$(CONFIG_BATTERY_S3C_ADC) += s3c_adc_battery.o
|
||||
obj-$(CONFIG_BATTERY_TWL4030_MADC) += twl4030_madc_battery.o
|
||||
obj-$(CONFIG_CHARGER_88PM860X) += 88pm860x_charger.o
|
||||
@ -58,9 +59,12 @@ obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o
|
||||
obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o
|
||||
obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o
|
||||
obj-$(CONFIG_CHARGER_BQ24190) += bq24190_charger.o
|
||||
obj-$(CONFIG_CHARGER_BQ24257) += bq24257_charger.o
|
||||
obj-$(CONFIG_CHARGER_BQ24735) += bq24735-charger.o
|
||||
obj-$(CONFIG_CHARGER_BQ25890) += bq25890_charger.o
|
||||
obj-$(CONFIG_POWER_AVS) += avs/
|
||||
obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o
|
||||
obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o
|
||||
obj-$(CONFIG_POWER_RESET) += reset/
|
||||
obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o
|
||||
obj-$(CONFIG_AXP288_CHARGER) += axp288_charger.o
|
||||
|
941
drivers/power/axp288_charger.c
Normal file
941
drivers/power/axp288_charger.c
Normal file
@ -0,0 +1,941 @@
|
||||
/*
|
||||
* axp288_charger.c - X-power AXP288 PMIC Charger driver
|
||||
*
|
||||
* Copyright (C) 2014 Intel Corporation
|
||||
* Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/mfd/axp20x.h>
|
||||
#include <linux/extcon.h>
|
||||
|
||||
#define PS_STAT_VBUS_TRIGGER (1 << 0)
|
||||
#define PS_STAT_BAT_CHRG_DIR (1 << 2)
|
||||
#define PS_STAT_VBAT_ABOVE_VHOLD (1 << 3)
|
||||
#define PS_STAT_VBUS_VALID (1 << 4)
|
||||
#define PS_STAT_VBUS_PRESENT (1 << 5)
|
||||
|
||||
#define CHRG_STAT_BAT_SAFE_MODE (1 << 3)
|
||||
#define CHRG_STAT_BAT_VALID (1 << 4)
|
||||
#define CHRG_STAT_BAT_PRESENT (1 << 5)
|
||||
#define CHRG_STAT_CHARGING (1 << 6)
|
||||
#define CHRG_STAT_PMIC_OTP (1 << 7)
|
||||
|
||||
#define VBUS_ISPOUT_CUR_LIM_MASK 0x03
|
||||
#define VBUS_ISPOUT_CUR_LIM_BIT_POS 0
|
||||
#define VBUS_ISPOUT_CUR_LIM_900MA 0x0 /* 900mA */
|
||||
#define VBUS_ISPOUT_CUR_LIM_1500MA 0x1 /* 1500mA */
|
||||
#define VBUS_ISPOUT_CUR_LIM_2000MA 0x2 /* 2000mA */
|
||||
#define VBUS_ISPOUT_CUR_NO_LIM 0x3 /* 2500mA */
|
||||
#define VBUS_ISPOUT_VHOLD_SET_MASK 0x31
|
||||
#define VBUS_ISPOUT_VHOLD_SET_BIT_POS 0x3
|
||||
#define VBUS_ISPOUT_VHOLD_SET_OFFSET 4000 /* 4000mV */
|
||||
#define VBUS_ISPOUT_VHOLD_SET_LSB_RES 100 /* 100mV */
|
||||
#define VBUS_ISPOUT_VHOLD_SET_4300MV 0x3 /* 4300mV */
|
||||
#define VBUS_ISPOUT_VBUS_PATH_DIS (1 << 7)
|
||||
|
||||
#define CHRG_CCCV_CC_MASK 0xf /* 4 bits */
|
||||
#define CHRG_CCCV_CC_BIT_POS 0
|
||||
#define CHRG_CCCV_CC_OFFSET 200 /* 200mA */
|
||||
#define CHRG_CCCV_CC_LSB_RES 200 /* 200mA */
|
||||
#define CHRG_CCCV_ITERM_20P (1 << 4) /* 20% of CC */
|
||||
#define CHRG_CCCV_CV_MASK 0x60 /* 2 bits */
|
||||
#define CHRG_CCCV_CV_BIT_POS 5
|
||||
#define CHRG_CCCV_CV_4100MV 0x0 /* 4.10V */
|
||||
#define CHRG_CCCV_CV_4150MV 0x1 /* 4.15V */
|
||||
#define CHRG_CCCV_CV_4200MV 0x2 /* 4.20V */
|
||||
#define CHRG_CCCV_CV_4350MV 0x3 /* 4.35V */
|
||||
#define CHRG_CCCV_CHG_EN (1 << 7)
|
||||
|
||||
#define CNTL2_CC_TIMEOUT_MASK 0x3 /* 2 bits */
|
||||
#define CNTL2_CC_TIMEOUT_OFFSET 6 /* 6 Hrs */
|
||||
#define CNTL2_CC_TIMEOUT_LSB_RES 2 /* 2 Hrs */
|
||||
#define CNTL2_CC_TIMEOUT_12HRS 0x3 /* 12 Hrs */
|
||||
#define CNTL2_CHGLED_TYPEB (1 << 4)
|
||||
#define CNTL2_CHG_OUT_TURNON (1 << 5)
|
||||
#define CNTL2_PC_TIMEOUT_MASK 0xC0
|
||||
#define CNTL2_PC_TIMEOUT_OFFSET 40 /* 40 mins */
|
||||
#define CNTL2_PC_TIMEOUT_LSB_RES 10 /* 10 mins */
|
||||
#define CNTL2_PC_TIMEOUT_70MINS 0x3
|
||||
|
||||
#define CHRG_ILIM_TEMP_LOOP_EN (1 << 3)
|
||||
#define CHRG_VBUS_ILIM_MASK 0xf0
|
||||
#define CHRG_VBUS_ILIM_BIT_POS 4
|
||||
#define CHRG_VBUS_ILIM_100MA 0x0 /* 100mA */
|
||||
#define CHRG_VBUS_ILIM_500MA 0x1 /* 500mA */
|
||||
#define CHRG_VBUS_ILIM_900MA 0x2 /* 900mA */
|
||||
#define CHRG_VBUS_ILIM_1500MA 0x3 /* 1500mA */
|
||||
#define CHRG_VBUS_ILIM_2000MA 0x4 /* 2000mA */
|
||||
#define CHRG_VBUS_ILIM_2500MA 0x5 /* 2500mA */
|
||||
#define CHRG_VBUS_ILIM_3000MA 0x6 /* 3000mA */
|
||||
|
||||
#define CHRG_VLTFC_0C 0xA5 /* 0 DegC */
|
||||
#define CHRG_VHTFC_45C 0x1F /* 45 DegC */
|
||||
|
||||
#define BAT_IRQ_CFG_CHRG_DONE (1 << 2)
|
||||
#define BAT_IRQ_CFG_CHRG_START (1 << 3)
|
||||
#define BAT_IRQ_CFG_BAT_SAFE_EXIT (1 << 4)
|
||||
#define BAT_IRQ_CFG_BAT_SAFE_ENTER (1 << 5)
|
||||
#define BAT_IRQ_CFG_BAT_DISCON (1 << 6)
|
||||
#define BAT_IRQ_CFG_BAT_CONN (1 << 7)
|
||||
#define BAT_IRQ_CFG_BAT_MASK 0xFC
|
||||
|
||||
#define TEMP_IRQ_CFG_QCBTU (1 << 4)
|
||||
#define TEMP_IRQ_CFG_CBTU (1 << 5)
|
||||
#define TEMP_IRQ_CFG_QCBTO (1 << 6)
|
||||
#define TEMP_IRQ_CFG_CBTO (1 << 7)
|
||||
#define TEMP_IRQ_CFG_MASK 0xF0
|
||||
|
||||
#define FG_CNTL_OCV_ADJ_EN (1 << 3)
|
||||
|
||||
#define CV_4100MV 4100 /* 4100mV */
|
||||
#define CV_4150MV 4150 /* 4150mV */
|
||||
#define CV_4200MV 4200 /* 4200mV */
|
||||
#define CV_4350MV 4350 /* 4350mV */
|
||||
|
||||
#define CC_200MA 200 /* 200mA */
|
||||
#define CC_600MA 600 /* 600mA */
|
||||
#define CC_800MA 800 /* 800mA */
|
||||
#define CC_1000MA 1000 /* 1000mA */
|
||||
#define CC_1600MA 1600 /* 1600mA */
|
||||
#define CC_2000MA 2000 /* 2000mA */
|
||||
|
||||
#define ILIM_100MA 100 /* 100mA */
|
||||
#define ILIM_500MA 500 /* 500mA */
|
||||
#define ILIM_900MA 900 /* 900mA */
|
||||
#define ILIM_1500MA 1500 /* 1500mA */
|
||||
#define ILIM_2000MA 2000 /* 2000mA */
|
||||
#define ILIM_2500MA 2500 /* 2500mA */
|
||||
#define ILIM_3000MA 3000 /* 3000mA */
|
||||
|
||||
#define AXP288_EXTCON_DEV_NAME "axp288_extcon"
|
||||
|
||||
#define AXP288_EXTCON_SLOW_CHARGER "SLOW-CHARGER"
|
||||
#define AXP288_EXTCON_DOWNSTREAM_CHARGER "CHARGE-DOWNSTREAM"
|
||||
#define AXP288_EXTCON_FAST_CHARGER "FAST-CHARGER"
|
||||
|
||||
enum {
|
||||
VBUS_OV_IRQ = 0,
|
||||
CHARGE_DONE_IRQ,
|
||||
CHARGE_CHARGING_IRQ,
|
||||
BAT_SAFE_QUIT_IRQ,
|
||||
BAT_SAFE_ENTER_IRQ,
|
||||
QCBTU_IRQ,
|
||||
CBTU_IRQ,
|
||||
QCBTO_IRQ,
|
||||
CBTO_IRQ,
|
||||
CHRG_INTR_END,
|
||||
};
|
||||
|
||||
struct axp288_chrg_info {
|
||||
struct platform_device *pdev;
|
||||
struct axp20x_chrg_pdata *pdata;
|
||||
struct regmap *regmap;
|
||||
struct regmap_irq_chip_data *regmap_irqc;
|
||||
int irq[CHRG_INTR_END];
|
||||
struct power_supply *psy_usb;
|
||||
struct mutex lock;
|
||||
|
||||
/* OTG/Host mode */
|
||||
struct {
|
||||
struct work_struct work;
|
||||
struct extcon_specific_cable_nb cable;
|
||||
struct notifier_block id_nb;
|
||||
bool id_short;
|
||||
} otg;
|
||||
|
||||
/* SDP/CDP/DCP USB charging cable notifications */
|
||||
struct {
|
||||
struct extcon_dev *edev;
|
||||
bool connected;
|
||||
enum power_supply_type chg_type;
|
||||
struct notifier_block nb;
|
||||
struct work_struct work;
|
||||
} cable;
|
||||
|
||||
int health;
|
||||
int inlmt;
|
||||
int cc;
|
||||
int cv;
|
||||
int max_cc;
|
||||
int max_cv;
|
||||
bool online;
|
||||
bool present;
|
||||
bool enable_charger;
|
||||
bool is_charger_enabled;
|
||||
};
|
||||
|
||||
static inline int axp288_charger_set_cc(struct axp288_chrg_info *info, int cc)
|
||||
{
|
||||
u8 reg_val;
|
||||
int ret;
|
||||
|
||||
if (cc < CHRG_CCCV_CC_OFFSET)
|
||||
cc = CHRG_CCCV_CC_OFFSET;
|
||||
else if (cc > info->max_cc)
|
||||
cc = info->max_cc;
|
||||
|
||||
reg_val = (cc - CHRG_CCCV_CC_OFFSET) / CHRG_CCCV_CC_LSB_RES;
|
||||
cc = (reg_val * CHRG_CCCV_CC_LSB_RES) + CHRG_CCCV_CC_OFFSET;
|
||||
reg_val = reg_val << CHRG_CCCV_CC_BIT_POS;
|
||||
|
||||
ret = regmap_update_bits(info->regmap,
|
||||
AXP20X_CHRG_CTRL1,
|
||||
CHRG_CCCV_CC_MASK, reg_val);
|
||||
if (ret >= 0)
|
||||
info->cc = cc;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int axp288_charger_set_cv(struct axp288_chrg_info *info, int cv)
|
||||
{
|
||||
u8 reg_val;
|
||||
int ret;
|
||||
|
||||
if (cv <= CV_4100MV) {
|
||||
reg_val = CHRG_CCCV_CV_4100MV;
|
||||
cv = CV_4100MV;
|
||||
} else if (cv <= CV_4150MV) {
|
||||
reg_val = CHRG_CCCV_CV_4150MV;
|
||||
cv = CV_4150MV;
|
||||
} else if (cv <= CV_4200MV) {
|
||||
reg_val = CHRG_CCCV_CV_4200MV;
|
||||
cv = CV_4200MV;
|
||||
} else {
|
||||
reg_val = CHRG_CCCV_CV_4350MV;
|
||||
cv = CV_4350MV;
|
||||
}
|
||||
|
||||
reg_val = reg_val << CHRG_CCCV_CV_BIT_POS;
|
||||
|
||||
ret = regmap_update_bits(info->regmap,
|
||||
AXP20X_CHRG_CTRL1,
|
||||
CHRG_CCCV_CV_MASK, reg_val);
|
||||
|
||||
if (ret >= 0)
|
||||
info->cv = cv;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int axp288_charger_set_vbus_inlmt(struct axp288_chrg_info *info,
|
||||
int inlmt)
|
||||
{
|
||||
int ret;
|
||||
unsigned int val;
|
||||
u8 reg_val;
|
||||
|
||||
/* Read in limit register */
|
||||
ret = regmap_read(info->regmap, AXP20X_CHRG_BAK_CTRL, &val);
|
||||
if (ret < 0)
|
||||
goto set_inlmt_fail;
|
||||
|
||||
if (inlmt <= ILIM_100MA) {
|
||||
reg_val = CHRG_VBUS_ILIM_100MA;
|
||||
inlmt = ILIM_100MA;
|
||||
} else if (inlmt <= ILIM_500MA) {
|
||||
reg_val = CHRG_VBUS_ILIM_500MA;
|
||||
inlmt = ILIM_500MA;
|
||||
} else if (inlmt <= ILIM_900MA) {
|
||||
reg_val = CHRG_VBUS_ILIM_900MA;
|
||||
inlmt = ILIM_900MA;
|
||||
} else if (inlmt <= ILIM_1500MA) {
|
||||
reg_val = CHRG_VBUS_ILIM_1500MA;
|
||||
inlmt = ILIM_1500MA;
|
||||
} else if (inlmt <= ILIM_2000MA) {
|
||||
reg_val = CHRG_VBUS_ILIM_2000MA;
|
||||
inlmt = ILIM_2000MA;
|
||||
} else if (inlmt <= ILIM_2500MA) {
|
||||
reg_val = CHRG_VBUS_ILIM_2500MA;
|
||||
inlmt = ILIM_2500MA;
|
||||
} else {
|
||||
reg_val = CHRG_VBUS_ILIM_3000MA;
|
||||
inlmt = ILIM_3000MA;
|
||||
}
|
||||
|
||||
reg_val = (val & ~CHRG_VBUS_ILIM_MASK)
|
||||
| (reg_val << CHRG_VBUS_ILIM_BIT_POS);
|
||||
ret = regmap_write(info->regmap, AXP20X_CHRG_BAK_CTRL, reg_val);
|
||||
if (ret >= 0)
|
||||
info->inlmt = inlmt;
|
||||
else
|
||||
dev_err(&info->pdev->dev, "charger BAK control %d\n", ret);
|
||||
|
||||
|
||||
set_inlmt_fail:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int axp288_charger_vbus_path_select(struct axp288_chrg_info *info,
|
||||
bool enable)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (enable)
|
||||
ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT,
|
||||
VBUS_ISPOUT_VBUS_PATH_DIS, 0);
|
||||
else
|
||||
ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT,
|
||||
VBUS_ISPOUT_VBUS_PATH_DIS, VBUS_ISPOUT_VBUS_PATH_DIS);
|
||||
|
||||
if (ret < 0)
|
||||
dev_err(&info->pdev->dev, "axp288 vbus path select %d\n", ret);
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int axp288_charger_enable_charger(struct axp288_chrg_info *info,
|
||||
bool enable)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (enable)
|
||||
ret = regmap_update_bits(info->regmap, AXP20X_CHRG_CTRL1,
|
||||
CHRG_CCCV_CHG_EN, CHRG_CCCV_CHG_EN);
|
||||
else
|
||||
ret = regmap_update_bits(info->regmap, AXP20X_CHRG_CTRL1,
|
||||
CHRG_CCCV_CHG_EN, 0);
|
||||
if (ret < 0)
|
||||
dev_err(&info->pdev->dev, "axp288 enable charger %d\n", ret);
|
||||
else
|
||||
info->is_charger_enabled = enable;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int axp288_charger_is_present(struct axp288_chrg_info *info)
|
||||
{
|
||||
int ret, present = 0;
|
||||
unsigned int val;
|
||||
|
||||
ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (val & PS_STAT_VBUS_PRESENT)
|
||||
present = 1;
|
||||
return present;
|
||||
}
|
||||
|
||||
static int axp288_charger_is_online(struct axp288_chrg_info *info)
|
||||
{
|
||||
int ret, online = 0;
|
||||
unsigned int val;
|
||||
|
||||
ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (val & PS_STAT_VBUS_VALID)
|
||||
online = 1;
|
||||
return online;
|
||||
}
|
||||
|
||||
static int axp288_get_charger_health(struct axp288_chrg_info *info)
|
||||
{
|
||||
int ret, pwr_stat, chrg_stat;
|
||||
int health = POWER_SUPPLY_HEALTH_UNKNOWN;
|
||||
unsigned int val;
|
||||
|
||||
ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val);
|
||||
if ((ret < 0) || !(val & PS_STAT_VBUS_PRESENT))
|
||||
goto health_read_fail;
|
||||
else
|
||||
pwr_stat = val;
|
||||
|
||||
ret = regmap_read(info->regmap, AXP20X_PWR_OP_MODE, &val);
|
||||
if (ret < 0)
|
||||
goto health_read_fail;
|
||||
else
|
||||
chrg_stat = val;
|
||||
|
||||
if (!(pwr_stat & PS_STAT_VBUS_VALID))
|
||||
health = POWER_SUPPLY_HEALTH_DEAD;
|
||||
else if (chrg_stat & CHRG_STAT_PMIC_OTP)
|
||||
health = POWER_SUPPLY_HEALTH_OVERHEAT;
|
||||
else if (chrg_stat & CHRG_STAT_BAT_SAFE_MODE)
|
||||
health = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
|
||||
else
|
||||
health = POWER_SUPPLY_HEALTH_GOOD;
|
||||
|
||||
health_read_fail:
|
||||
return health;
|
||||
}
|
||||
|
||||
static int axp288_charger_usb_set_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
const union power_supply_propval *val)
|
||||
{
|
||||
struct axp288_chrg_info *info = power_supply_get_drvdata(psy);
|
||||
int ret = 0;
|
||||
int scaled_val;
|
||||
|
||||
mutex_lock(&info->lock);
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
||||
scaled_val = min(val->intval, info->max_cc);
|
||||
scaled_val = DIV_ROUND_CLOSEST(scaled_val, 1000);
|
||||
ret = axp288_charger_set_cc(info, scaled_val);
|
||||
if (ret < 0)
|
||||
dev_warn(&info->pdev->dev, "set charge current failed\n");
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
|
||||
scaled_val = min(val->intval, info->max_cv);
|
||||
scaled_val = DIV_ROUND_CLOSEST(scaled_val, 1000);
|
||||
ret = axp288_charger_set_cv(info, scaled_val);
|
||||
if (ret < 0)
|
||||
dev_warn(&info->pdev->dev, "set charge voltage failed\n");
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
mutex_unlock(&info->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int axp288_charger_usb_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct axp288_chrg_info *info = power_supply_get_drvdata(psy);
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&info->lock);
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_PRESENT:
|
||||
/* Check for OTG case first */
|
||||
if (info->otg.id_short) {
|
||||
val->intval = 0;
|
||||
break;
|
||||
}
|
||||
ret = axp288_charger_is_present(info);
|
||||
if (ret < 0)
|
||||
goto psy_get_prop_fail;
|
||||
info->present = ret;
|
||||
val->intval = info->present;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
/* Check for OTG case first */
|
||||
if (info->otg.id_short) {
|
||||
val->intval = 0;
|
||||
break;
|
||||
}
|
||||
ret = axp288_charger_is_online(info);
|
||||
if (ret < 0)
|
||||
goto psy_get_prop_fail;
|
||||
info->online = ret;
|
||||
val->intval = info->online;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_HEALTH:
|
||||
val->intval = axp288_get_charger_health(info);
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
||||
val->intval = info->cc * 1000;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
||||
val->intval = info->max_cc * 1000;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
|
||||
val->intval = info->cv * 1000;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
|
||||
val->intval = info->max_cv * 1000;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
|
||||
val->intval = info->inlmt * 1000;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
goto psy_get_prop_fail;
|
||||
}
|
||||
|
||||
psy_get_prop_fail:
|
||||
mutex_unlock(&info->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int axp288_charger_property_is_writeable(struct power_supply *psy,
|
||||
enum power_supply_property psp)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
|
||||
ret = 1;
|
||||
break;
|
||||
default:
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum power_supply_property axp288_usb_props[] = {
|
||||
POWER_SUPPLY_PROP_PRESENT,
|
||||
POWER_SUPPLY_PROP_ONLINE,
|
||||
POWER_SUPPLY_PROP_TYPE,
|
||||
POWER_SUPPLY_PROP_HEALTH,
|
||||
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
|
||||
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
|
||||
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
|
||||
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
|
||||
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
|
||||
};
|
||||
|
||||
static const struct power_supply_desc axp288_charger_desc = {
|
||||
.name = "axp288_charger",
|
||||
.type = POWER_SUPPLY_TYPE_USB,
|
||||
.properties = axp288_usb_props,
|
||||
.num_properties = ARRAY_SIZE(axp288_usb_props),
|
||||
.get_property = axp288_charger_usb_get_property,
|
||||
.set_property = axp288_charger_usb_set_property,
|
||||
.property_is_writeable = axp288_charger_property_is_writeable,
|
||||
};
|
||||
|
||||
static irqreturn_t axp288_charger_irq_thread_handler(int irq, void *dev)
|
||||
{
|
||||
struct axp288_chrg_info *info = dev;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < CHRG_INTR_END; i++) {
|
||||
if (info->irq[i] == irq)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i >= CHRG_INTR_END) {
|
||||
dev_warn(&info->pdev->dev, "spurious interrupt!!\n");
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
switch (i) {
|
||||
case VBUS_OV_IRQ:
|
||||
dev_dbg(&info->pdev->dev, "VBUS Over Voltage INTR\n");
|
||||
break;
|
||||
case CHARGE_DONE_IRQ:
|
||||
dev_dbg(&info->pdev->dev, "Charging Done INTR\n");
|
||||
break;
|
||||
case CHARGE_CHARGING_IRQ:
|
||||
dev_dbg(&info->pdev->dev, "Start Charging IRQ\n");
|
||||
break;
|
||||
case BAT_SAFE_QUIT_IRQ:
|
||||
dev_dbg(&info->pdev->dev,
|
||||
"Quit Safe Mode(restart timer) Charging IRQ\n");
|
||||
break;
|
||||
case BAT_SAFE_ENTER_IRQ:
|
||||
dev_dbg(&info->pdev->dev,
|
||||
"Enter Safe Mode(timer expire) Charging IRQ\n");
|
||||
break;
|
||||
case QCBTU_IRQ:
|
||||
dev_dbg(&info->pdev->dev,
|
||||
"Quit Battery Under Temperature(CHRG) INTR\n");
|
||||
break;
|
||||
case CBTU_IRQ:
|
||||
dev_dbg(&info->pdev->dev,
|
||||
"Hit Battery Under Temperature(CHRG) INTR\n");
|
||||
break;
|
||||
case QCBTO_IRQ:
|
||||
dev_dbg(&info->pdev->dev,
|
||||
"Quit Battery Over Temperature(CHRG) INTR\n");
|
||||
break;
|
||||
case CBTO_IRQ:
|
||||
dev_dbg(&info->pdev->dev,
|
||||
"Hit Battery Over Temperature(CHRG) INTR\n");
|
||||
break;
|
||||
default:
|
||||
dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
power_supply_changed(info->psy_usb);
|
||||
out:
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void axp288_charger_extcon_evt_worker(struct work_struct *work)
|
||||
{
|
||||
struct axp288_chrg_info *info =
|
||||
container_of(work, struct axp288_chrg_info, cable.work);
|
||||
int ret, current_limit;
|
||||
bool changed = false;
|
||||
struct extcon_dev *edev = info->cable.edev;
|
||||
bool old_connected = info->cable.connected;
|
||||
|
||||
/* Determine cable/charger type */
|
||||
if (extcon_get_cable_state(edev, AXP288_EXTCON_SLOW_CHARGER) > 0) {
|
||||
dev_dbg(&info->pdev->dev, "USB SDP charger is connected");
|
||||
info->cable.connected = true;
|
||||
info->cable.chg_type = POWER_SUPPLY_TYPE_USB;
|
||||
} else if (extcon_get_cable_state(edev,
|
||||
AXP288_EXTCON_DOWNSTREAM_CHARGER) > 0) {
|
||||
dev_dbg(&info->pdev->dev, "USB CDP charger is connected");
|
||||
info->cable.connected = true;
|
||||
info->cable.chg_type = POWER_SUPPLY_TYPE_USB_CDP;
|
||||
} else if (extcon_get_cable_state(edev,
|
||||
AXP288_EXTCON_FAST_CHARGER) > 0) {
|
||||
dev_dbg(&info->pdev->dev, "USB DCP charger is connected");
|
||||
info->cable.connected = true;
|
||||
info->cable.chg_type = POWER_SUPPLY_TYPE_USB_DCP;
|
||||
} else {
|
||||
if (old_connected)
|
||||
dev_dbg(&info->pdev->dev, "USB charger disconnected");
|
||||
info->cable.connected = false;
|
||||
info->cable.chg_type = POWER_SUPPLY_TYPE_USB;
|
||||
}
|
||||
|
||||
/* Cable status changed */
|
||||
if (old_connected != info->cable.connected)
|
||||
changed = true;
|
||||
|
||||
if (!changed)
|
||||
return;
|
||||
|
||||
mutex_lock(&info->lock);
|
||||
|
||||
if (info->is_charger_enabled && !info->cable.connected) {
|
||||
info->enable_charger = false;
|
||||
ret = axp288_charger_enable_charger(info, info->enable_charger);
|
||||
if (ret < 0)
|
||||
dev_err(&info->pdev->dev,
|
||||
"cannot disable charger (%d)", ret);
|
||||
|
||||
} else if (!info->is_charger_enabled && info->cable.connected) {
|
||||
switch (info->cable.chg_type) {
|
||||
case POWER_SUPPLY_TYPE_USB:
|
||||
current_limit = ILIM_500MA;
|
||||
break;
|
||||
case POWER_SUPPLY_TYPE_USB_CDP:
|
||||
current_limit = ILIM_1500MA;
|
||||
break;
|
||||
case POWER_SUPPLY_TYPE_USB_DCP:
|
||||
current_limit = ILIM_2000MA;
|
||||
break;
|
||||
default:
|
||||
/* Unknown */
|
||||
current_limit = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Set vbus current limit first, then enable charger */
|
||||
ret = axp288_charger_set_vbus_inlmt(info, current_limit);
|
||||
if (ret < 0) {
|
||||
dev_err(&info->pdev->dev,
|
||||
"error setting current limit (%d)", ret);
|
||||
} else {
|
||||
info->enable_charger = (current_limit > 0);
|
||||
ret = axp288_charger_enable_charger(info,
|
||||
info->enable_charger);
|
||||
if (ret < 0)
|
||||
dev_err(&info->pdev->dev,
|
||||
"cannot enable charger (%d)", ret);
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
info->health = axp288_get_charger_health(info);
|
||||
|
||||
mutex_unlock(&info->lock);
|
||||
|
||||
if (changed)
|
||||
power_supply_changed(info->psy_usb);
|
||||
}
|
||||
|
||||
static int axp288_charger_handle_cable_evt(struct notifier_block *nb,
|
||||
unsigned long event, void *param)
|
||||
{
|
||||
struct axp288_chrg_info *info =
|
||||
container_of(nb, struct axp288_chrg_info, cable.nb);
|
||||
|
||||
schedule_work(&info->cable.work);
|
||||
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static void axp288_charger_otg_evt_worker(struct work_struct *work)
|
||||
{
|
||||
struct axp288_chrg_info *info =
|
||||
container_of(work, struct axp288_chrg_info, otg.work);
|
||||
int ret;
|
||||
|
||||
/* Disable VBUS path before enabling the 5V boost */
|
||||
ret = axp288_charger_vbus_path_select(info, !info->otg.id_short);
|
||||
if (ret < 0)
|
||||
dev_warn(&info->pdev->dev, "vbus path disable failed\n");
|
||||
}
|
||||
|
||||
static int axp288_charger_handle_otg_evt(struct notifier_block *nb,
|
||||
unsigned long event, void *param)
|
||||
{
|
||||
struct axp288_chrg_info *info =
|
||||
container_of(nb, struct axp288_chrg_info, otg.id_nb);
|
||||
struct extcon_dev *edev = param;
|
||||
int usb_host = extcon_get_cable_state(edev, "USB-Host");
|
||||
|
||||
dev_dbg(&info->pdev->dev, "external connector USB-Host is %s\n",
|
||||
usb_host ? "attached" : "detached");
|
||||
|
||||
/*
|
||||
* Set usb_id_short flag to avoid running charger detection logic
|
||||
* in case usb host.
|
||||
*/
|
||||
info->otg.id_short = usb_host;
|
||||
schedule_work(&info->otg.work);
|
||||
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static void charger_init_hw_regs(struct axp288_chrg_info *info)
|
||||
{
|
||||
int ret, cc, cv;
|
||||
unsigned int val;
|
||||
|
||||
/* Program temperature thresholds */
|
||||
ret = regmap_write(info->regmap, AXP20X_V_LTF_CHRG, CHRG_VLTFC_0C);
|
||||
if (ret < 0)
|
||||
dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
|
||||
AXP20X_V_LTF_CHRG, ret);
|
||||
|
||||
ret = regmap_write(info->regmap, AXP20X_V_HTF_CHRG, CHRG_VHTFC_45C);
|
||||
if (ret < 0)
|
||||
dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
|
||||
AXP20X_V_HTF_CHRG, ret);
|
||||
|
||||
/* Do not turn-off charger o/p after charge cycle ends */
|
||||
ret = regmap_update_bits(info->regmap,
|
||||
AXP20X_CHRG_CTRL2,
|
||||
CNTL2_CHG_OUT_TURNON, 1);
|
||||
if (ret < 0)
|
||||
dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
|
||||
AXP20X_CHRG_CTRL2, ret);
|
||||
|
||||
/* Enable interrupts */
|
||||
ret = regmap_update_bits(info->regmap,
|
||||
AXP20X_IRQ2_EN,
|
||||
BAT_IRQ_CFG_BAT_MASK, 1);
|
||||
if (ret < 0)
|
||||
dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
|
||||
AXP20X_IRQ2_EN, ret);
|
||||
|
||||
ret = regmap_update_bits(info->regmap, AXP20X_IRQ3_EN,
|
||||
TEMP_IRQ_CFG_MASK, 1);
|
||||
if (ret < 0)
|
||||
dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
|
||||
AXP20X_IRQ3_EN, ret);
|
||||
|
||||
/* Setup ending condition for charging to be 10% of I(chrg) */
|
||||
ret = regmap_update_bits(info->regmap,
|
||||
AXP20X_CHRG_CTRL1,
|
||||
CHRG_CCCV_ITERM_20P, 0);
|
||||
if (ret < 0)
|
||||
dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
|
||||
AXP20X_CHRG_CTRL1, ret);
|
||||
|
||||
/* Disable OCV-SOC curve calibration */
|
||||
ret = regmap_update_bits(info->regmap,
|
||||
AXP20X_CC_CTRL,
|
||||
FG_CNTL_OCV_ADJ_EN, 0);
|
||||
if (ret < 0)
|
||||
dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
|
||||
AXP20X_CC_CTRL, ret);
|
||||
|
||||
/* Init charging current and voltage */
|
||||
info->max_cc = info->pdata->max_cc;
|
||||
info->max_cv = info->pdata->max_cv;
|
||||
|
||||
/* Read current charge voltage and current limit */
|
||||
ret = regmap_read(info->regmap, AXP20X_CHRG_CTRL1, &val);
|
||||
if (ret < 0) {
|
||||
/* Assume default if cannot read */
|
||||
info->cc = info->pdata->def_cc;
|
||||
info->cv = info->pdata->def_cv;
|
||||
} else {
|
||||
/* Determine charge voltage */
|
||||
cv = (val & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS;
|
||||
switch (cv) {
|
||||
case CHRG_CCCV_CV_4100MV:
|
||||
info->cv = CV_4100MV;
|
||||
break;
|
||||
case CHRG_CCCV_CV_4150MV:
|
||||
info->cv = CV_4150MV;
|
||||
break;
|
||||
case CHRG_CCCV_CV_4200MV:
|
||||
info->cv = CV_4200MV;
|
||||
break;
|
||||
case CHRG_CCCV_CV_4350MV:
|
||||
info->cv = CV_4350MV;
|
||||
break;
|
||||
default:
|
||||
info->cv = INT_MAX;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Determine charge current limit */
|
||||
cc = (ret & CHRG_CCCV_CC_MASK) >> CHRG_CCCV_CC_BIT_POS;
|
||||
cc = (cc * CHRG_CCCV_CC_LSB_RES) + CHRG_CCCV_CC_OFFSET;
|
||||
info->cc = cc;
|
||||
|
||||
/* Program default charging voltage and current */
|
||||
cc = min(info->pdata->def_cc, info->max_cc);
|
||||
cv = min(info->pdata->def_cv, info->max_cv);
|
||||
|
||||
ret = axp288_charger_set_cc(info, cc);
|
||||
if (ret < 0)
|
||||
dev_warn(&info->pdev->dev,
|
||||
"error(%d) in setting CC\n", ret);
|
||||
|
||||
ret = axp288_charger_set_cv(info, cv);
|
||||
if (ret < 0)
|
||||
dev_warn(&info->pdev->dev,
|
||||
"error(%d) in setting CV\n", ret);
|
||||
}
|
||||
}
|
||||
|
||||
static int axp288_charger_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret, i, pirq;
|
||||
struct axp288_chrg_info *info;
|
||||
struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
|
||||
struct power_supply_config charger_cfg = {};
|
||||
|
||||
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
info->pdev = pdev;
|
||||
info->regmap = axp20x->regmap;
|
||||
info->regmap_irqc = axp20x->regmap_irqc;
|
||||
info->pdata = pdev->dev.platform_data;
|
||||
|
||||
if (!info->pdata) {
|
||||
/* Try ACPI provided pdata via device properties */
|
||||
if (!device_property_present(&pdev->dev,
|
||||
"axp288_charger_data\n"))
|
||||
dev_err(&pdev->dev, "failed to get platform data\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
info->cable.edev = extcon_get_extcon_dev(AXP288_EXTCON_DEV_NAME);
|
||||
if (info->cable.edev == NULL) {
|
||||
dev_dbg(&pdev->dev, "%s is not ready, probe deferred\n",
|
||||
AXP288_EXTCON_DEV_NAME);
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
/* Register for extcon notification */
|
||||
INIT_WORK(&info->cable.work, axp288_charger_extcon_evt_worker);
|
||||
info->cable.nb.notifier_call = axp288_charger_handle_cable_evt;
|
||||
ret = extcon_register_notifier(info->cable.edev, &info->cable.nb);
|
||||
if (ret) {
|
||||
dev_err(&info->pdev->dev,
|
||||
"failed to register extcon notifier %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, info);
|
||||
mutex_init(&info->lock);
|
||||
|
||||
/* Register with power supply class */
|
||||
charger_cfg.drv_data = info;
|
||||
info->psy_usb = power_supply_register(&pdev->dev, &axp288_charger_desc,
|
||||
&charger_cfg);
|
||||
if (IS_ERR(info->psy_usb)) {
|
||||
dev_err(&pdev->dev, "failed to register power supply charger\n");
|
||||
ret = PTR_ERR(info->psy_usb);
|
||||
goto psy_reg_failed;
|
||||
}
|
||||
|
||||
/* Register for OTG notification */
|
||||
INIT_WORK(&info->otg.work, axp288_charger_otg_evt_worker);
|
||||
info->otg.id_nb.notifier_call = axp288_charger_handle_otg_evt;
|
||||
ret = extcon_register_interest(&info->otg.cable, NULL, "USB-Host",
|
||||
&info->otg.id_nb);
|
||||
if (ret)
|
||||
dev_warn(&pdev->dev, "failed to register otg notifier\n");
|
||||
|
||||
if (info->otg.cable.edev)
|
||||
info->otg.id_short = extcon_get_cable_state(
|
||||
info->otg.cable.edev, "USB-Host");
|
||||
|
||||
/* Register charger interrupts */
|
||||
for (i = 0; i < CHRG_INTR_END; i++) {
|
||||
pirq = platform_get_irq(info->pdev, i);
|
||||
info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
|
||||
if (info->irq[i] < 0) {
|
||||
dev_warn(&info->pdev->dev,
|
||||
"failed to get virtual interrupt=%d\n", pirq);
|
||||
ret = info->irq[i];
|
||||
goto intr_reg_failed;
|
||||
}
|
||||
ret = devm_request_threaded_irq(&info->pdev->dev, info->irq[i],
|
||||
NULL, axp288_charger_irq_thread_handler,
|
||||
IRQF_ONESHOT, info->pdev->name, info);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to request interrupt=%d\n",
|
||||
info->irq[i]);
|
||||
goto intr_reg_failed;
|
||||
}
|
||||
}
|
||||
|
||||
charger_init_hw_regs(info);
|
||||
|
||||
return 0;
|
||||
|
||||
intr_reg_failed:
|
||||
if (info->otg.cable.edev)
|
||||
extcon_unregister_interest(&info->otg.cable);
|
||||
power_supply_unregister(info->psy_usb);
|
||||
psy_reg_failed:
|
||||
extcon_unregister_notifier(info->cable.edev, &info->cable.nb);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int axp288_charger_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct axp288_chrg_info *info = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
if (info->otg.cable.edev)
|
||||
extcon_unregister_interest(&info->otg.cable);
|
||||
|
||||
extcon_unregister_notifier(info->cable.edev, &info->cable.nb);
|
||||
power_supply_unregister(info->psy_usb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver axp288_charger_driver = {
|
||||
.probe = axp288_charger_probe,
|
||||
.remove = axp288_charger_remove,
|
||||
.driver = {
|
||||
.name = "axp288_charger",
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(axp288_charger_driver);
|
||||
|
||||
MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
|
||||
MODULE_DESCRIPTION("X-power AXP288 Charger Driver");
|
||||
MODULE_LICENSE("GPL v2");
|
@ -1117,7 +1117,7 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct platform_device_id axp288_fg_id_table[] = {
|
||||
static const struct platform_device_id axp288_fg_id_table[] = {
|
||||
{ .name = DEV_NAME },
|
||||
{},
|
||||
};
|
||||
|
@ -35,6 +35,7 @@
|
||||
#include <linux/idr.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/acpi.h>
|
||||
|
||||
#include <linux/power/bq2415x_charger.h>
|
||||
|
||||
@ -631,7 +632,7 @@ static int bq2415x_set_charge_current(struct bq2415x_device *bq, int mA)
|
||||
int val;
|
||||
|
||||
if (bq->init_data.resistor_sense <= 0)
|
||||
return -ENOSYS;
|
||||
return -EINVAL;
|
||||
|
||||
val = (mA * bq->init_data.resistor_sense - 37400) / 6800;
|
||||
if (val < 0)
|
||||
@ -650,7 +651,7 @@ static int bq2415x_get_charge_current(struct bq2415x_device *bq)
|
||||
int ret;
|
||||
|
||||
if (bq->init_data.resistor_sense <= 0)
|
||||
return -ENOSYS;
|
||||
return -EINVAL;
|
||||
|
||||
ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CURRENT,
|
||||
BQ2415X_MASK_VI_CHRG, BQ2415X_SHIFT_VI_CHRG);
|
||||
@ -665,7 +666,7 @@ static int bq2415x_set_termination_current(struct bq2415x_device *bq, int mA)
|
||||
int val;
|
||||
|
||||
if (bq->init_data.resistor_sense <= 0)
|
||||
return -ENOSYS;
|
||||
return -EINVAL;
|
||||
|
||||
val = (mA * bq->init_data.resistor_sense - 3400) / 3400;
|
||||
if (val < 0)
|
||||
@ -684,7 +685,7 @@ static int bq2415x_get_termination_current(struct bq2415x_device *bq)
|
||||
int ret;
|
||||
|
||||
if (bq->init_data.resistor_sense <= 0)
|
||||
return -ENOSYS;
|
||||
return -EINVAL;
|
||||
|
||||
ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CURRENT,
|
||||
BQ2415X_MASK_VI_TERM, BQ2415X_SHIFT_VI_TERM);
|
||||
@ -1166,7 +1167,7 @@ static ssize_t bq2415x_sysfs_set_mode(struct device *dev,
|
||||
|
||||
if (strncmp(buf, "auto", 4) == 0) {
|
||||
if (bq->automode < 0)
|
||||
return -ENOSYS;
|
||||
return -EINVAL;
|
||||
bq->automode = 1;
|
||||
mode = bq->reported_mode;
|
||||
} else if (strncmp(buf, "off", 3) == 0) {
|
||||
@ -1530,13 +1531,14 @@ static int bq2415x_probe(struct i2c_client *client,
|
||||
{
|
||||
int ret;
|
||||
int num;
|
||||
char *name;
|
||||
char *name = NULL;
|
||||
struct bq2415x_device *bq;
|
||||
struct device_node *np = client->dev.of_node;
|
||||
struct bq2415x_platform_data *pdata = client->dev.platform_data;
|
||||
const struct acpi_device_id *acpi_id = NULL;
|
||||
|
||||
if (!np && !pdata) {
|
||||
dev_err(&client->dev, "platform data missing\n");
|
||||
if (!np && !pdata && !ACPI_HANDLE(&client->dev)) {
|
||||
dev_err(&client->dev, "Neither devicetree, nor platform data, nor ACPI support\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
@ -1547,7 +1549,14 @@ static int bq2415x_probe(struct i2c_client *client,
|
||||
if (num < 0)
|
||||
return num;
|
||||
|
||||
name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num);
|
||||
if (id) {
|
||||
name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num);
|
||||
} else if (ACPI_HANDLE(&client->dev)) {
|
||||
acpi_id =
|
||||
acpi_match_device(client->dev.driver->acpi_match_table,
|
||||
&client->dev);
|
||||
name = kasprintf(GFP_KERNEL, "%s-%d", acpi_id->id, num);
|
||||
}
|
||||
if (!name) {
|
||||
dev_err(&client->dev, "failed to allocate device name\n");
|
||||
ret = -ENOMEM;
|
||||
@ -1556,63 +1565,72 @@ static int bq2415x_probe(struct i2c_client *client,
|
||||
|
||||
bq = devm_kzalloc(&client->dev, sizeof(*bq), GFP_KERNEL);
|
||||
if (!bq) {
|
||||
dev_err(&client->dev, "failed to allocate device data\n");
|
||||
ret = -ENOMEM;
|
||||
goto error_2;
|
||||
}
|
||||
|
||||
if (np) {
|
||||
bq->notify_psy = power_supply_get_by_phandle(np, "ti,usb-charger-detection");
|
||||
bq->notify_psy = power_supply_get_by_phandle(np,
|
||||
"ti,usb-charger-detection");
|
||||
|
||||
if (IS_ERR(bq->notify_psy)) {
|
||||
dev_info(&client->dev,
|
||||
"no 'ti,usb-charger-detection' property (err=%ld)\n",
|
||||
"no 'ti,usb-charger-detection' property (err=%ld)\n",
|
||||
PTR_ERR(bq->notify_psy));
|
||||
bq->notify_psy = NULL;
|
||||
} else if (!bq->notify_psy) {
|
||||
ret = -EPROBE_DEFER;
|
||||
goto error_2;
|
||||
}
|
||||
}
|
||||
else if (pdata->notify_device)
|
||||
} else if (pdata && pdata->notify_device) {
|
||||
bq->notify_psy = power_supply_get_by_name(pdata->notify_device);
|
||||
else
|
||||
} else {
|
||||
bq->notify_psy = NULL;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(client, bq);
|
||||
|
||||
bq->id = num;
|
||||
bq->dev = &client->dev;
|
||||
bq->chip = id->driver_data;
|
||||
if (id)
|
||||
bq->chip = id->driver_data;
|
||||
else if (ACPI_HANDLE(bq->dev))
|
||||
bq->chip = acpi_id->driver_data;
|
||||
bq->name = name;
|
||||
bq->mode = BQ2415X_MODE_OFF;
|
||||
bq->reported_mode = BQ2415X_MODE_OFF;
|
||||
bq->autotimer = 0;
|
||||
bq->automode = 0;
|
||||
|
||||
if (np) {
|
||||
ret = of_property_read_u32(np, "ti,current-limit",
|
||||
&bq->init_data.current_limit);
|
||||
if (np || ACPI_HANDLE(bq->dev)) {
|
||||
ret = device_property_read_u32(bq->dev,
|
||||
"ti,current-limit",
|
||||
&bq->init_data.current_limit);
|
||||
if (ret)
|
||||
goto error_3;
|
||||
ret = of_property_read_u32(np, "ti,weak-battery-voltage",
|
||||
&bq->init_data.weak_battery_voltage);
|
||||
ret = device_property_read_u32(bq->dev,
|
||||
"ti,weak-battery-voltage",
|
||||
&bq->init_data.weak_battery_voltage);
|
||||
if (ret)
|
||||
goto error_3;
|
||||
ret = of_property_read_u32(np, "ti,battery-regulation-voltage",
|
||||
ret = device_property_read_u32(bq->dev,
|
||||
"ti,battery-regulation-voltage",
|
||||
&bq->init_data.battery_regulation_voltage);
|
||||
if (ret)
|
||||
goto error_3;
|
||||
ret = of_property_read_u32(np, "ti,charge-current",
|
||||
&bq->init_data.charge_current);
|
||||
ret = device_property_read_u32(bq->dev,
|
||||
"ti,charge-current",
|
||||
&bq->init_data.charge_current);
|
||||
if (ret)
|
||||
goto error_3;
|
||||
ret = of_property_read_u32(np, "ti,termination-current",
|
||||
ret = device_property_read_u32(bq->dev,
|
||||
"ti,termination-current",
|
||||
&bq->init_data.termination_current);
|
||||
if (ret)
|
||||
goto error_3;
|
||||
ret = of_property_read_u32(np, "ti,resistor-sense",
|
||||
&bq->init_data.resistor_sense);
|
||||
ret = device_property_read_u32(bq->dev,
|
||||
"ti,resistor-sense",
|
||||
&bq->init_data.resistor_sense);
|
||||
if (ret)
|
||||
goto error_3;
|
||||
} else {
|
||||
@ -1648,7 +1666,8 @@ static int bq2415x_probe(struct i2c_client *client,
|
||||
}
|
||||
|
||||
/* Query for initial reported_mode and set it */
|
||||
bq2415x_notifier_call(&bq->nb, PSY_EVENT_PROP_CHANGED, bq->notify_psy);
|
||||
bq2415x_notifier_call(&bq->nb, PSY_EVENT_PROP_CHANGED,
|
||||
bq->notify_psy);
|
||||
bq2415x_set_mode(bq, bq->reported_mode);
|
||||
|
||||
bq->automode = 1;
|
||||
@ -1727,9 +1746,28 @@ static const struct i2c_device_id bq2415x_i2c_id_table[] = {
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, bq2415x_i2c_id_table);
|
||||
|
||||
static const struct acpi_device_id bq2415x_i2c_acpi_match[] = {
|
||||
{ "BQ2415X", BQUNKNOWN },
|
||||
{ "BQ241500", BQ24150 },
|
||||
{ "BQA24150", BQ24150A },
|
||||
{ "BQ241510", BQ24151 },
|
||||
{ "BQA24151", BQ24151A },
|
||||
{ "BQ241520", BQ24152 },
|
||||
{ "BQ241530", BQ24153 },
|
||||
{ "BQA24153", BQ24153A },
|
||||
{ "BQ241550", BQ24155 },
|
||||
{ "BQ241560", BQ24156 },
|
||||
{ "BQA24156", BQ24156A },
|
||||
{ "BQS24157", BQ24157S },
|
||||
{ "BQ241580", BQ24158 },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, bq2415x_i2c_acpi_match);
|
||||
|
||||
static struct i2c_driver bq2415x_driver = {
|
||||
.driver = {
|
||||
.name = "bq2415x-charger",
|
||||
.acpi_match_table = ACPI_PTR(bq2415x_i2c_acpi_match),
|
||||
},
|
||||
.probe = bq2415x_probe,
|
||||
.remove = bq2415x_remove,
|
||||
|
@ -1258,10 +1258,13 @@ static irqreturn_t bq24190_irq_handler_thread(int irq, void *data)
|
||||
* register reset so we should ignore that one (the very first
|
||||
* interrupt received).
|
||||
*/
|
||||
if (alert_userspace && !bdi->first_time) {
|
||||
power_supply_changed(bdi->charger);
|
||||
power_supply_changed(bdi->battery);
|
||||
bdi->first_time = false;
|
||||
if (alert_userspace) {
|
||||
if (!bdi->first_time) {
|
||||
power_supply_changed(bdi->charger);
|
||||
power_supply_changed(bdi->battery);
|
||||
} else {
|
||||
bdi->first_time = false;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
|
858
drivers/power/bq24257_charger.c
Normal file
858
drivers/power/bq24257_charger.c
Normal file
@ -0,0 +1,858 @@
|
||||
/*
|
||||
* TI BQ24257 charger driver
|
||||
*
|
||||
* Copyright (C) 2015 Intel Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#define BQ24257_REG_1 0x00
|
||||
#define BQ24257_REG_2 0x01
|
||||
#define BQ24257_REG_3 0x02
|
||||
#define BQ24257_REG_4 0x03
|
||||
#define BQ24257_REG_5 0x04
|
||||
#define BQ24257_REG_6 0x05
|
||||
#define BQ24257_REG_7 0x06
|
||||
|
||||
#define BQ24257_MANUFACTURER "Texas Instruments"
|
||||
#define BQ24257_STAT_IRQ "stat"
|
||||
#define BQ24257_PG_GPIO "pg"
|
||||
|
||||
#define BQ24257_ILIM_SET_DELAY 1000 /* msec */
|
||||
|
||||
enum bq24257_fields {
|
||||
F_WD_FAULT, F_WD_EN, F_STAT, F_FAULT, /* REG 1 */
|
||||
F_RESET, F_IILIMIT, F_EN_STAT, F_EN_TERM, F_CE, F_HZ_MODE, /* REG 2 */
|
||||
F_VBAT, F_USB_DET, /* REG 3 */
|
||||
F_ICHG, F_ITERM, /* REG 4 */
|
||||
F_LOOP_STATUS, F_LOW_CHG, F_DPDM_EN, F_CE_STATUS, F_VINDPM, /* REG 5 */
|
||||
F_X2_TMR_EN, F_TMR, F_SYSOFF, F_TS_STAT, /* REG 6 */
|
||||
F_VOVP, F_CLR_VDP, F_FORCE_BATDET, F_FORCE_PTM, /* REG 7 */
|
||||
|
||||
F_MAX_FIELDS
|
||||
};
|
||||
|
||||
/* initial field values, converted from uV/uA */
|
||||
struct bq24257_init_data {
|
||||
u8 ichg; /* charge current */
|
||||
u8 vbat; /* regulation voltage */
|
||||
u8 iterm; /* termination current */
|
||||
};
|
||||
|
||||
struct bq24257_state {
|
||||
u8 status;
|
||||
u8 fault;
|
||||
bool power_good;
|
||||
};
|
||||
|
||||
struct bq24257_device {
|
||||
struct i2c_client *client;
|
||||
struct device *dev;
|
||||
struct power_supply *charger;
|
||||
|
||||
struct regmap *rmap;
|
||||
struct regmap_field *rmap_fields[F_MAX_FIELDS];
|
||||
|
||||
struct gpio_desc *pg;
|
||||
|
||||
struct delayed_work iilimit_setup_work;
|
||||
|
||||
struct bq24257_init_data init_data;
|
||||
struct bq24257_state state;
|
||||
|
||||
struct mutex lock; /* protect state data */
|
||||
};
|
||||
|
||||
static bool bq24257_is_volatile_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case BQ24257_REG_2:
|
||||
case BQ24257_REG_4:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct regmap_config bq24257_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
|
||||
.max_register = BQ24257_REG_7,
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
|
||||
.volatile_reg = bq24257_is_volatile_reg,
|
||||
};
|
||||
|
||||
static const struct reg_field bq24257_reg_fields[] = {
|
||||
/* REG 1 */
|
||||
[F_WD_FAULT] = REG_FIELD(BQ24257_REG_1, 7, 7),
|
||||
[F_WD_EN] = REG_FIELD(BQ24257_REG_1, 6, 6),
|
||||
[F_STAT] = REG_FIELD(BQ24257_REG_1, 4, 5),
|
||||
[F_FAULT] = REG_FIELD(BQ24257_REG_1, 0, 3),
|
||||
/* REG 2 */
|
||||
[F_RESET] = REG_FIELD(BQ24257_REG_2, 7, 7),
|
||||
[F_IILIMIT] = REG_FIELD(BQ24257_REG_2, 4, 6),
|
||||
[F_EN_STAT] = REG_FIELD(BQ24257_REG_2, 3, 3),
|
||||
[F_EN_TERM] = REG_FIELD(BQ24257_REG_2, 2, 2),
|
||||
[F_CE] = REG_FIELD(BQ24257_REG_2, 1, 1),
|
||||
[F_HZ_MODE] = REG_FIELD(BQ24257_REG_2, 0, 0),
|
||||
/* REG 3 */
|
||||
[F_VBAT] = REG_FIELD(BQ24257_REG_3, 2, 7),
|
||||
[F_USB_DET] = REG_FIELD(BQ24257_REG_3, 0, 1),
|
||||
/* REG 4 */
|
||||
[F_ICHG] = REG_FIELD(BQ24257_REG_4, 3, 7),
|
||||
[F_ITERM] = REG_FIELD(BQ24257_REG_4, 0, 2),
|
||||
/* REG 5 */
|
||||
[F_LOOP_STATUS] = REG_FIELD(BQ24257_REG_5, 6, 7),
|
||||
[F_LOW_CHG] = REG_FIELD(BQ24257_REG_5, 5, 5),
|
||||
[F_DPDM_EN] = REG_FIELD(BQ24257_REG_5, 4, 4),
|
||||
[F_CE_STATUS] = REG_FIELD(BQ24257_REG_5, 3, 3),
|
||||
[F_VINDPM] = REG_FIELD(BQ24257_REG_5, 0, 2),
|
||||
/* REG 6 */
|
||||
[F_X2_TMR_EN] = REG_FIELD(BQ24257_REG_6, 7, 7),
|
||||
[F_TMR] = REG_FIELD(BQ24257_REG_6, 5, 6),
|
||||
[F_SYSOFF] = REG_FIELD(BQ24257_REG_6, 4, 4),
|
||||
[F_TS_STAT] = REG_FIELD(BQ24257_REG_6, 0, 2),
|
||||
/* REG 7 */
|
||||
[F_VOVP] = REG_FIELD(BQ24257_REG_7, 5, 7),
|
||||
[F_CLR_VDP] = REG_FIELD(BQ24257_REG_7, 4, 4),
|
||||
[F_FORCE_BATDET] = REG_FIELD(BQ24257_REG_7, 3, 3),
|
||||
[F_FORCE_PTM] = REG_FIELD(BQ24257_REG_7, 2, 2)
|
||||
};
|
||||
|
||||
static const u32 bq24257_vbat_map[] = {
|
||||
3500000, 3520000, 3540000, 3560000, 3580000, 3600000, 3620000, 3640000,
|
||||
3660000, 3680000, 3700000, 3720000, 3740000, 3760000, 3780000, 3800000,
|
||||
3820000, 3840000, 3860000, 3880000, 3900000, 3920000, 3940000, 3960000,
|
||||
3980000, 4000000, 4020000, 4040000, 4060000, 4080000, 4100000, 4120000,
|
||||
4140000, 4160000, 4180000, 4200000, 4220000, 4240000, 4260000, 4280000,
|
||||
4300000, 4320000, 4340000, 4360000, 4380000, 4400000, 4420000, 4440000
|
||||
};
|
||||
|
||||
#define BQ24257_VBAT_MAP_SIZE ARRAY_SIZE(bq24257_vbat_map)
|
||||
|
||||
static const u32 bq24257_ichg_map[] = {
|
||||
500000, 550000, 600000, 650000, 700000, 750000, 800000, 850000, 900000,
|
||||
950000, 1000000, 1050000, 1100000, 1150000, 1200000, 1250000, 1300000,
|
||||
1350000, 1400000, 1450000, 1500000, 1550000, 1600000, 1650000, 1700000,
|
||||
1750000, 1800000, 1850000, 1900000, 1950000, 2000000
|
||||
};
|
||||
|
||||
#define BQ24257_ICHG_MAP_SIZE ARRAY_SIZE(bq24257_ichg_map)
|
||||
|
||||
static const u32 bq24257_iterm_map[] = {
|
||||
50000, 75000, 100000, 125000, 150000, 175000, 200000, 225000
|
||||
};
|
||||
|
||||
#define BQ24257_ITERM_MAP_SIZE ARRAY_SIZE(bq24257_iterm_map)
|
||||
|
||||
static int bq24257_field_read(struct bq24257_device *bq,
|
||||
enum bq24257_fields field_id)
|
||||
{
|
||||
int ret;
|
||||
int val;
|
||||
|
||||
ret = regmap_field_read(bq->rmap_fields[field_id], &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static int bq24257_field_write(struct bq24257_device *bq,
|
||||
enum bq24257_fields field_id, u8 val)
|
||||
{
|
||||
return regmap_field_write(bq->rmap_fields[field_id], val);
|
||||
}
|
||||
|
||||
static u8 bq24257_find_idx(u32 value, const u32 *map, u8 map_size)
|
||||
{
|
||||
u8 idx;
|
||||
|
||||
for (idx = 1; idx < map_size; idx++)
|
||||
if (value < map[idx])
|
||||
break;
|
||||
|
||||
return idx - 1;
|
||||
}
|
||||
|
||||
enum bq24257_status {
|
||||
STATUS_READY,
|
||||
STATUS_CHARGE_IN_PROGRESS,
|
||||
STATUS_CHARGE_DONE,
|
||||
STATUS_FAULT,
|
||||
};
|
||||
|
||||
enum bq24257_fault {
|
||||
FAULT_NORMAL,
|
||||
FAULT_INPUT_OVP,
|
||||
FAULT_INPUT_UVLO,
|
||||
FAULT_SLEEP,
|
||||
FAULT_BAT_TS,
|
||||
FAULT_BAT_OVP,
|
||||
FAULT_TS,
|
||||
FAULT_TIMER,
|
||||
FAULT_NO_BAT,
|
||||
FAULT_ISET,
|
||||
FAULT_INPUT_LDO_LOW,
|
||||
};
|
||||
|
||||
static int bq24257_power_supply_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct bq24257_device *bq = power_supply_get_drvdata(psy);
|
||||
struct bq24257_state state;
|
||||
|
||||
mutex_lock(&bq->lock);
|
||||
state = bq->state;
|
||||
mutex_unlock(&bq->lock);
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
if (!state.power_good)
|
||||
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
else if (state.status == STATUS_READY)
|
||||
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
||||
else if (state.status == STATUS_CHARGE_IN_PROGRESS)
|
||||
val->intval = POWER_SUPPLY_STATUS_CHARGING;
|
||||
else if (state.status == STATUS_CHARGE_DONE)
|
||||
val->intval = POWER_SUPPLY_STATUS_FULL;
|
||||
else
|
||||
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_MANUFACTURER:
|
||||
val->strval = BQ24257_MANUFACTURER;
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
val->intval = state.power_good;
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_HEALTH:
|
||||
switch (state.fault) {
|
||||
case FAULT_NORMAL:
|
||||
val->intval = POWER_SUPPLY_HEALTH_GOOD;
|
||||
break;
|
||||
|
||||
case FAULT_INPUT_OVP:
|
||||
case FAULT_BAT_OVP:
|
||||
val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
|
||||
break;
|
||||
|
||||
case FAULT_TS:
|
||||
case FAULT_BAT_TS:
|
||||
val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
|
||||
break;
|
||||
|
||||
case FAULT_TIMER:
|
||||
val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
|
||||
break;
|
||||
|
||||
default:
|
||||
val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
||||
val->intval = bq24257_ichg_map[bq->init_data.ichg];
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
||||
val->intval = bq24257_ichg_map[BQ24257_ICHG_MAP_SIZE - 1];
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
|
||||
val->intval = bq24257_vbat_map[bq->init_data.vbat];
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
|
||||
val->intval = bq24257_vbat_map[BQ24257_VBAT_MAP_SIZE - 1];
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
|
||||
val->intval = bq24257_iterm_map[bq->init_data.iterm];
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bq24257_get_chip_state(struct bq24257_device *bq,
|
||||
struct bq24257_state *state)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = bq24257_field_read(bq, F_STAT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
state->status = ret;
|
||||
|
||||
ret = bq24257_field_read(bq, F_FAULT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
state->fault = ret;
|
||||
|
||||
state->power_good = !gpiod_get_value_cansleep(bq->pg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool bq24257_state_changed(struct bq24257_device *bq,
|
||||
struct bq24257_state *new_state)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&bq->lock);
|
||||
ret = (bq->state.status != new_state->status ||
|
||||
bq->state.fault != new_state->fault ||
|
||||
bq->state.power_good != new_state->power_good);
|
||||
mutex_unlock(&bq->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
enum bq24257_loop_status {
|
||||
LOOP_STATUS_NONE,
|
||||
LOOP_STATUS_IN_DPM,
|
||||
LOOP_STATUS_IN_CURRENT_LIMIT,
|
||||
LOOP_STATUS_THERMAL,
|
||||
};
|
||||
|
||||
enum bq24257_in_ilimit {
|
||||
IILIMIT_100,
|
||||
IILIMIT_150,
|
||||
IILIMIT_500,
|
||||
IILIMIT_900,
|
||||
IILIMIT_1500,
|
||||
IILIMIT_2000,
|
||||
IILIMIT_EXT,
|
||||
IILIMIT_NONE,
|
||||
};
|
||||
|
||||
enum bq24257_port_type {
|
||||
PORT_TYPE_DCP, /* Dedicated Charging Port */
|
||||
PORT_TYPE_CDP, /* Charging Downstream Port */
|
||||
PORT_TYPE_SDP, /* Standard Downstream Port */
|
||||
PORT_TYPE_NON_STANDARD,
|
||||
};
|
||||
|
||||
enum bq24257_safety_timer {
|
||||
SAFETY_TIMER_45,
|
||||
SAFETY_TIMER_360,
|
||||
SAFETY_TIMER_540,
|
||||
SAFETY_TIMER_NONE,
|
||||
};
|
||||
|
||||
static int bq24257_iilimit_autoset(struct bq24257_device *bq)
|
||||
{
|
||||
int loop_status;
|
||||
int iilimit;
|
||||
int port_type;
|
||||
int ret;
|
||||
const u8 new_iilimit[] = {
|
||||
[PORT_TYPE_DCP] = IILIMIT_2000,
|
||||
[PORT_TYPE_CDP] = IILIMIT_2000,
|
||||
[PORT_TYPE_SDP] = IILIMIT_500,
|
||||
[PORT_TYPE_NON_STANDARD] = IILIMIT_500
|
||||
};
|
||||
|
||||
ret = bq24257_field_read(bq, F_LOOP_STATUS);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
loop_status = ret;
|
||||
|
||||
ret = bq24257_field_read(bq, F_IILIMIT);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
iilimit = ret;
|
||||
|
||||
/*
|
||||
* All USB ports should be able to handle 500mA. If not, DPM will lower
|
||||
* the charging current to accommodate the power source. No need to set
|
||||
* a lower IILIMIT value.
|
||||
*/
|
||||
if (loop_status == LOOP_STATUS_IN_DPM && iilimit == IILIMIT_500)
|
||||
return 0;
|
||||
|
||||
ret = bq24257_field_read(bq, F_USB_DET);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
port_type = ret;
|
||||
|
||||
ret = bq24257_field_write(bq, F_IILIMIT, new_iilimit[port_type]);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
ret = bq24257_field_write(bq, F_TMR, SAFETY_TIMER_360);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
ret = bq24257_field_write(bq, F_CLR_VDP, 1);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
dev_dbg(bq->dev, "port/loop = %d/%d -> iilimit = %d\n",
|
||||
port_type, loop_status, new_iilimit[port_type]);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
dev_err(bq->dev, "%s: Error communicating with the chip.\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void bq24257_iilimit_setup_work(struct work_struct *work)
|
||||
{
|
||||
struct bq24257_device *bq = container_of(work, struct bq24257_device,
|
||||
iilimit_setup_work.work);
|
||||
|
||||
bq24257_iilimit_autoset(bq);
|
||||
}
|
||||
|
||||
static void bq24257_handle_state_change(struct bq24257_device *bq,
|
||||
struct bq24257_state *new_state)
|
||||
{
|
||||
int ret;
|
||||
struct bq24257_state old_state;
|
||||
bool reset_iilimit = false;
|
||||
bool config_iilimit = false;
|
||||
|
||||
mutex_lock(&bq->lock);
|
||||
old_state = bq->state;
|
||||
mutex_unlock(&bq->lock);
|
||||
|
||||
if (!new_state->power_good) { /* power removed */
|
||||
cancel_delayed_work_sync(&bq->iilimit_setup_work);
|
||||
|
||||
/* activate D+/D- port detection algorithm */
|
||||
ret = bq24257_field_write(bq, F_DPDM_EN, 1);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
reset_iilimit = true;
|
||||
} else if (!old_state.power_good) { /* power inserted */
|
||||
config_iilimit = true;
|
||||
} else if (new_state->fault == FAULT_NO_BAT) { /* battery removed */
|
||||
cancel_delayed_work_sync(&bq->iilimit_setup_work);
|
||||
|
||||
reset_iilimit = true;
|
||||
} else if (old_state.fault == FAULT_NO_BAT) { /* battery connected */
|
||||
config_iilimit = true;
|
||||
} else if (new_state->fault == FAULT_TIMER) { /* safety timer expired */
|
||||
dev_err(bq->dev, "Safety timer expired! Battery dead?\n");
|
||||
}
|
||||
|
||||
if (reset_iilimit) {
|
||||
ret = bq24257_field_write(bq, F_IILIMIT, IILIMIT_500);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
} else if (config_iilimit) {
|
||||
schedule_delayed_work(&bq->iilimit_setup_work,
|
||||
msecs_to_jiffies(BQ24257_ILIM_SET_DELAY));
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
dev_err(bq->dev, "%s: Error communicating with the chip.\n", __func__);
|
||||
}
|
||||
|
||||
static irqreturn_t bq24257_irq_handler_thread(int irq, void *private)
|
||||
{
|
||||
int ret;
|
||||
struct bq24257_device *bq = private;
|
||||
struct bq24257_state state;
|
||||
|
||||
ret = bq24257_get_chip_state(bq, &state);
|
||||
if (ret < 0)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
if (!bq24257_state_changed(bq, &state))
|
||||
return IRQ_HANDLED;
|
||||
|
||||
dev_dbg(bq->dev, "irq(state changed): status/fault/pg = %d/%d/%d\n",
|
||||
state.status, state.fault, state.power_good);
|
||||
|
||||
bq24257_handle_state_change(bq, &state);
|
||||
|
||||
mutex_lock(&bq->lock);
|
||||
bq->state = state;
|
||||
mutex_unlock(&bq->lock);
|
||||
|
||||
power_supply_changed(bq->charger);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int bq24257_hw_init(struct bq24257_device *bq)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
struct bq24257_state state;
|
||||
|
||||
const struct {
|
||||
int field;
|
||||
u32 value;
|
||||
} init_data[] = {
|
||||
{F_ICHG, bq->init_data.ichg},
|
||||
{F_VBAT, bq->init_data.vbat},
|
||||
{F_ITERM, bq->init_data.iterm}
|
||||
};
|
||||
|
||||
/*
|
||||
* Disable the watchdog timer to prevent the IC from going back to
|
||||
* default settings after 50 seconds of I2C inactivity.
|
||||
*/
|
||||
ret = bq24257_field_write(bq, F_WD_EN, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* configure the charge currents and voltages */
|
||||
for (i = 0; i < ARRAY_SIZE(init_data); i++) {
|
||||
ret = bq24257_field_write(bq, init_data[i].field,
|
||||
init_data[i].value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = bq24257_get_chip_state(bq, &state);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&bq->lock);
|
||||
bq->state = state;
|
||||
mutex_unlock(&bq->lock);
|
||||
|
||||
if (!state.power_good)
|
||||
/* activate D+/D- detection algorithm */
|
||||
ret = bq24257_field_write(bq, F_DPDM_EN, 1);
|
||||
else if (state.fault != FAULT_NO_BAT)
|
||||
ret = bq24257_iilimit_autoset(bq);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum power_supply_property bq24257_power_supply_props[] = {
|
||||
POWER_SUPPLY_PROP_MANUFACTURER,
|
||||
POWER_SUPPLY_PROP_STATUS,
|
||||
POWER_SUPPLY_PROP_ONLINE,
|
||||
POWER_SUPPLY_PROP_HEALTH,
|
||||
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
|
||||
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
|
||||
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
|
||||
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
|
||||
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
|
||||
};
|
||||
|
||||
static char *bq24257_charger_supplied_to[] = {
|
||||
"main-battery",
|
||||
};
|
||||
|
||||
static const struct power_supply_desc bq24257_power_supply_desc = {
|
||||
.name = "bq24257-charger",
|
||||
.type = POWER_SUPPLY_TYPE_USB,
|
||||
.properties = bq24257_power_supply_props,
|
||||
.num_properties = ARRAY_SIZE(bq24257_power_supply_props),
|
||||
.get_property = bq24257_power_supply_get_property,
|
||||
};
|
||||
|
||||
static int bq24257_power_supply_init(struct bq24257_device *bq)
|
||||
{
|
||||
struct power_supply_config psy_cfg = { .drv_data = bq, };
|
||||
|
||||
psy_cfg.supplied_to = bq24257_charger_supplied_to;
|
||||
psy_cfg.num_supplicants = ARRAY_SIZE(bq24257_charger_supplied_to);
|
||||
|
||||
bq->charger = power_supply_register(bq->dev, &bq24257_power_supply_desc,
|
||||
&psy_cfg);
|
||||
if (IS_ERR(bq->charger))
|
||||
return PTR_ERR(bq->charger);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bq24257_irq_probe(struct bq24257_device *bq)
|
||||
{
|
||||
struct gpio_desc *stat_irq;
|
||||
|
||||
stat_irq = devm_gpiod_get_index(bq->dev, BQ24257_STAT_IRQ, 0, GPIOD_IN);
|
||||
if (IS_ERR(stat_irq)) {
|
||||
dev_err(bq->dev, "could not probe stat_irq pin\n");
|
||||
return PTR_ERR(stat_irq);
|
||||
}
|
||||
|
||||
return gpiod_to_irq(stat_irq);
|
||||
}
|
||||
|
||||
static int bq24257_pg_gpio_probe(struct bq24257_device *bq)
|
||||
{
|
||||
bq->pg = devm_gpiod_get_index(bq->dev, BQ24257_PG_GPIO, 0, GPIOD_IN);
|
||||
if (IS_ERR(bq->pg)) {
|
||||
dev_err(bq->dev, "could not probe PG pin\n");
|
||||
return PTR_ERR(bq->pg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bq24257_fw_probe(struct bq24257_device *bq)
|
||||
{
|
||||
int ret;
|
||||
u32 property;
|
||||
|
||||
ret = device_property_read_u32(bq->dev, "ti,charge-current", &property);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
bq->init_data.ichg = bq24257_find_idx(property, bq24257_ichg_map,
|
||||
BQ24257_ICHG_MAP_SIZE);
|
||||
|
||||
ret = device_property_read_u32(bq->dev, "ti,battery-regulation-voltage",
|
||||
&property);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
bq->init_data.vbat = bq24257_find_idx(property, bq24257_vbat_map,
|
||||
BQ24257_VBAT_MAP_SIZE);
|
||||
|
||||
ret = device_property_read_u32(bq->dev, "ti,termination-current",
|
||||
&property);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
bq->init_data.iterm = bq24257_find_idx(property, bq24257_iterm_map,
|
||||
BQ24257_ITERM_MAP_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bq24257_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
|
||||
struct device *dev = &client->dev;
|
||||
struct bq24257_device *bq;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
|
||||
dev_err(dev, "No support for SMBUS_BYTE_DATA\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
bq = devm_kzalloc(dev, sizeof(*bq), GFP_KERNEL);
|
||||
if (!bq)
|
||||
return -ENOMEM;
|
||||
|
||||
bq->client = client;
|
||||
bq->dev = dev;
|
||||
|
||||
mutex_init(&bq->lock);
|
||||
|
||||
bq->rmap = devm_regmap_init_i2c(client, &bq24257_regmap_config);
|
||||
if (IS_ERR(bq->rmap)) {
|
||||
dev_err(dev, "failed to allocate register map\n");
|
||||
return PTR_ERR(bq->rmap);
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(bq24257_reg_fields); i++) {
|
||||
const struct reg_field *reg_fields = bq24257_reg_fields;
|
||||
|
||||
bq->rmap_fields[i] = devm_regmap_field_alloc(dev, bq->rmap,
|
||||
reg_fields[i]);
|
||||
if (IS_ERR(bq->rmap_fields[i])) {
|
||||
dev_err(dev, "cannot allocate regmap field\n");
|
||||
return PTR_ERR(bq->rmap_fields[i]);
|
||||
}
|
||||
}
|
||||
|
||||
i2c_set_clientdata(client, bq);
|
||||
|
||||
INIT_DELAYED_WORK(&bq->iilimit_setup_work, bq24257_iilimit_setup_work);
|
||||
|
||||
if (!dev->platform_data) {
|
||||
ret = bq24257_fw_probe(bq);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Cannot read device properties.\n");
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* we can only check Power Good status by probing the PG pin */
|
||||
ret = bq24257_pg_gpio_probe(bq);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* reset all registers to defaults */
|
||||
ret = bq24257_field_write(bq, F_RESET, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Put the RESET bit back to 0, in cache. For some reason the HW always
|
||||
* returns 1 on this bit, so this is the only way to avoid resetting the
|
||||
* chip every time we update another field in this register.
|
||||
*/
|
||||
ret = bq24257_field_write(bq, F_RESET, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = bq24257_hw_init(bq);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Cannot initialize the chip.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (client->irq <= 0)
|
||||
client->irq = bq24257_irq_probe(bq);
|
||||
|
||||
if (client->irq < 0) {
|
||||
dev_err(dev, "no irq resource found\n");
|
||||
return client->irq;
|
||||
}
|
||||
|
||||
ret = devm_request_threaded_irq(dev, client->irq, NULL,
|
||||
bq24257_irq_handler_thread,
|
||||
IRQF_TRIGGER_FALLING |
|
||||
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
||||
BQ24257_STAT_IRQ, bq);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = bq24257_power_supply_init(bq);
|
||||
if (ret < 0)
|
||||
dev_err(dev, "Failed to register power supply\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bq24257_remove(struct i2c_client *client)
|
||||
{
|
||||
struct bq24257_device *bq = i2c_get_clientdata(client);
|
||||
|
||||
cancel_delayed_work_sync(&bq->iilimit_setup_work);
|
||||
|
||||
power_supply_unregister(bq->charger);
|
||||
|
||||
bq24257_field_write(bq, F_RESET, 1); /* reset to defaults */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int bq24257_suspend(struct device *dev)
|
||||
{
|
||||
struct bq24257_device *bq = dev_get_drvdata(dev);
|
||||
int ret = 0;
|
||||
|
||||
cancel_delayed_work_sync(&bq->iilimit_setup_work);
|
||||
|
||||
/* reset all registers to default (and activate standalone mode) */
|
||||
ret = bq24257_field_write(bq, F_RESET, 1);
|
||||
if (ret < 0)
|
||||
dev_err(bq->dev, "Cannot reset chip to standalone mode.\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bq24257_resume(struct device *dev)
|
||||
{
|
||||
int ret;
|
||||
struct bq24257_device *bq = dev_get_drvdata(dev);
|
||||
|
||||
ret = regcache_drop_region(bq->rmap, BQ24257_REG_1, BQ24257_REG_7);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = bq24257_field_write(bq, F_RESET, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = bq24257_hw_init(bq);
|
||||
if (ret < 0) {
|
||||
dev_err(bq->dev, "Cannot init chip after resume.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* signal userspace, maybe state changed while suspended */
|
||||
power_supply_changed(bq->charger);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops bq24257_pm = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(bq24257_suspend, bq24257_resume)
|
||||
};
|
||||
|
||||
static const struct i2c_device_id bq24257_i2c_ids[] = {
|
||||
{ "bq24257", 0 },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, bq24257_i2c_ids);
|
||||
|
||||
static const struct of_device_id bq24257_of_match[] = {
|
||||
{ .compatible = "ti,bq24257", },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, bq24257_of_match);
|
||||
|
||||
static const struct acpi_device_id bq24257_acpi_match[] = {
|
||||
{"BQ242570", 0},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, bq24257_acpi_match);
|
||||
|
||||
static struct i2c_driver bq24257_driver = {
|
||||
.driver = {
|
||||
.name = "bq24257-charger",
|
||||
.of_match_table = of_match_ptr(bq24257_of_match),
|
||||
.acpi_match_table = ACPI_PTR(bq24257_acpi_match),
|
||||
.pm = &bq24257_pm,
|
||||
},
|
||||
.probe = bq24257_probe,
|
||||
.remove = bq24257_remove,
|
||||
.id_table = bq24257_i2c_ids,
|
||||
};
|
||||
module_i2c_driver(bq24257_driver);
|
||||
|
||||
MODULE_AUTHOR("Laurentiu Palcu <laurentiu.palcu@intel.com>");
|
||||
MODULE_DESCRIPTION("bq24257 charger driver");
|
||||
MODULE_LICENSE("GPL");
|
994
drivers/power/bq25890_charger.c
Normal file
994
drivers/power/bq25890_charger.c
Normal file
@ -0,0 +1,994 @@
|
||||
/*
|
||||
* TI BQ25890 charger driver
|
||||
*
|
||||
* Copyright (C) 2015 Intel Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/usb/phy.h>
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#define BQ25890_MANUFACTURER "Texas Instruments"
|
||||
#define BQ25890_IRQ_PIN "bq25890_irq"
|
||||
|
||||
#define BQ25890_ID 3
|
||||
|
||||
enum bq25890_fields {
|
||||
F_EN_HIZ, F_EN_ILIM, F_IILIM, /* Reg00 */
|
||||
F_BHOT, F_BCOLD, F_VINDPM_OFS, /* Reg01 */
|
||||
F_CONV_START, F_CONV_RATE, F_BOOSTF, F_ICO_EN,
|
||||
F_HVDCP_EN, F_MAXC_EN, F_FORCE_DPM, F_AUTO_DPDM_EN, /* Reg02 */
|
||||
F_BAT_LOAD_EN, F_WD_RST, F_OTG_CFG, F_CHG_CFG, F_SYSVMIN, /* Reg03 */
|
||||
F_PUMPX_EN, F_ICHG, /* Reg04 */
|
||||
F_IPRECHG, F_ITERM, /* Reg05 */
|
||||
F_VREG, F_BATLOWV, F_VRECHG, /* Reg06 */
|
||||
F_TERM_EN, F_STAT_DIS, F_WD, F_TMR_EN, F_CHG_TMR,
|
||||
F_JEITA_ISET, /* Reg07 */
|
||||
F_BATCMP, F_VCLAMP, F_TREG, /* Reg08 */
|
||||
F_FORCE_ICO, F_TMR2X_EN, F_BATFET_DIS, F_JEITA_VSET,
|
||||
F_BATFET_DLY, F_BATFET_RST_EN, F_PUMPX_UP, F_PUMPX_DN, /* Reg09 */
|
||||
F_BOOSTV, F_BOOSTI, /* Reg0A */
|
||||
F_VBUS_STAT, F_CHG_STAT, F_PG_STAT, F_SDP_STAT, F_VSYS_STAT, /* Reg0B */
|
||||
F_WD_FAULT, F_BOOST_FAULT, F_CHG_FAULT, F_BAT_FAULT,
|
||||
F_NTC_FAULT, /* Reg0C */
|
||||
F_FORCE_VINDPM, F_VINDPM, /* Reg0D */
|
||||
F_THERM_STAT, F_BATV, /* Reg0E */
|
||||
F_SYSV, /* Reg0F */
|
||||
F_TSPCT, /* Reg10 */
|
||||
F_VBUS_GD, F_VBUSV, /* Reg11 */
|
||||
F_ICHGR, /* Reg12 */
|
||||
F_VDPM_STAT, F_IDPM_STAT, F_IDPM_LIM, /* Reg13 */
|
||||
F_REG_RST, F_ICO_OPTIMIZED, F_PN, F_TS_PROFILE, F_DEV_REV, /* Reg14 */
|
||||
|
||||
F_MAX_FIELDS
|
||||
};
|
||||
|
||||
/* initial field values, converted to register values */
|
||||
struct bq25890_init_data {
|
||||
u8 ichg; /* charge current */
|
||||
u8 vreg; /* regulation voltage */
|
||||
u8 iterm; /* termination current */
|
||||
u8 iprechg; /* precharge current */
|
||||
u8 sysvmin; /* minimum system voltage limit */
|
||||
u8 boostv; /* boost regulation voltage */
|
||||
u8 boosti; /* boost current limit */
|
||||
u8 boostf; /* boost frequency */
|
||||
u8 ilim_en; /* enable ILIM pin */
|
||||
u8 treg; /* thermal regulation threshold */
|
||||
};
|
||||
|
||||
struct bq25890_state {
|
||||
u8 online;
|
||||
u8 chrg_status;
|
||||
u8 chrg_fault;
|
||||
u8 vsys_status;
|
||||
u8 boost_fault;
|
||||
u8 bat_fault;
|
||||
};
|
||||
|
||||
struct bq25890_device {
|
||||
struct i2c_client *client;
|
||||
struct device *dev;
|
||||
struct power_supply *charger;
|
||||
|
||||
struct usb_phy *usb_phy;
|
||||
struct notifier_block usb_nb;
|
||||
struct work_struct usb_work;
|
||||
unsigned long usb_event;
|
||||
|
||||
struct regmap *rmap;
|
||||
struct regmap_field *rmap_fields[F_MAX_FIELDS];
|
||||
|
||||
int chip_id;
|
||||
struct bq25890_init_data init_data;
|
||||
struct bq25890_state state;
|
||||
|
||||
struct mutex lock; /* protect state data */
|
||||
};
|
||||
|
||||
static const struct regmap_range bq25890_readonly_reg_ranges[] = {
|
||||
regmap_reg_range(0x0b, 0x0c),
|
||||
regmap_reg_range(0x0e, 0x13),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table bq25890_writeable_regs = {
|
||||
.no_ranges = bq25890_readonly_reg_ranges,
|
||||
.n_no_ranges = ARRAY_SIZE(bq25890_readonly_reg_ranges),
|
||||
};
|
||||
|
||||
static const struct regmap_range bq25890_volatile_reg_ranges[] = {
|
||||
regmap_reg_range(0x00, 0x00),
|
||||
regmap_reg_range(0x09, 0x09),
|
||||
regmap_reg_range(0x0b, 0x0c),
|
||||
regmap_reg_range(0x0e, 0x14),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table bq25890_volatile_regs = {
|
||||
.yes_ranges = bq25890_volatile_reg_ranges,
|
||||
.n_yes_ranges = ARRAY_SIZE(bq25890_volatile_reg_ranges),
|
||||
};
|
||||
|
||||
static const struct regmap_config bq25890_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
|
||||
.max_register = 0x14,
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
|
||||
.wr_table = &bq25890_writeable_regs,
|
||||
.volatile_table = &bq25890_volatile_regs,
|
||||
};
|
||||
|
||||
static const struct reg_field bq25890_reg_fields[] = {
|
||||
/* REG00 */
|
||||
[F_EN_HIZ] = REG_FIELD(0x00, 7, 7),
|
||||
[F_EN_ILIM] = REG_FIELD(0x00, 6, 6),
|
||||
[F_IILIM] = REG_FIELD(0x00, 0, 5),
|
||||
/* REG01 */
|
||||
[F_BHOT] = REG_FIELD(0x01, 6, 7),
|
||||
[F_BCOLD] = REG_FIELD(0x01, 5, 5),
|
||||
[F_VINDPM_OFS] = REG_FIELD(0x01, 0, 4),
|
||||
/* REG02 */
|
||||
[F_CONV_START] = REG_FIELD(0x02, 7, 7),
|
||||
[F_CONV_RATE] = REG_FIELD(0x02, 6, 6),
|
||||
[F_BOOSTF] = REG_FIELD(0x02, 5, 5),
|
||||
[F_ICO_EN] = REG_FIELD(0x02, 4, 4),
|
||||
[F_HVDCP_EN] = REG_FIELD(0x02, 3, 3),
|
||||
[F_MAXC_EN] = REG_FIELD(0x02, 2, 2),
|
||||
[F_FORCE_DPM] = REG_FIELD(0x02, 1, 1),
|
||||
[F_AUTO_DPDM_EN] = REG_FIELD(0x02, 0, 0),
|
||||
/* REG03 */
|
||||
[F_BAT_LOAD_EN] = REG_FIELD(0x03, 7, 7),
|
||||
[F_WD_RST] = REG_FIELD(0x03, 6, 6),
|
||||
[F_OTG_CFG] = REG_FIELD(0x03, 5, 5),
|
||||
[F_CHG_CFG] = REG_FIELD(0x03, 4, 4),
|
||||
[F_SYSVMIN] = REG_FIELD(0x03, 1, 3),
|
||||
/* REG04 */
|
||||
[F_PUMPX_EN] = REG_FIELD(0x04, 7, 7),
|
||||
[F_ICHG] = REG_FIELD(0x04, 0, 6),
|
||||
/* REG05 */
|
||||
[F_IPRECHG] = REG_FIELD(0x05, 4, 7),
|
||||
[F_ITERM] = REG_FIELD(0x05, 0, 3),
|
||||
/* REG06 */
|
||||
[F_VREG] = REG_FIELD(0x06, 2, 7),
|
||||
[F_BATLOWV] = REG_FIELD(0x06, 1, 1),
|
||||
[F_VRECHG] = REG_FIELD(0x06, 0, 0),
|
||||
/* REG07 */
|
||||
[F_TERM_EN] = REG_FIELD(0x07, 7, 7),
|
||||
[F_STAT_DIS] = REG_FIELD(0x07, 6, 6),
|
||||
[F_WD] = REG_FIELD(0x07, 4, 5),
|
||||
[F_TMR_EN] = REG_FIELD(0x07, 3, 3),
|
||||
[F_CHG_TMR] = REG_FIELD(0x07, 1, 2),
|
||||
[F_JEITA_ISET] = REG_FIELD(0x07, 0, 0),
|
||||
/* REG08 */
|
||||
[F_BATCMP] = REG_FIELD(0x08, 6, 7),
|
||||
[F_VCLAMP] = REG_FIELD(0x08, 2, 4),
|
||||
[F_TREG] = REG_FIELD(0x08, 0, 1),
|
||||
/* REG09 */
|
||||
[F_FORCE_ICO] = REG_FIELD(0x09, 7, 7),
|
||||
[F_TMR2X_EN] = REG_FIELD(0x09, 6, 6),
|
||||
[F_BATFET_DIS] = REG_FIELD(0x09, 5, 5),
|
||||
[F_JEITA_VSET] = REG_FIELD(0x09, 4, 4),
|
||||
[F_BATFET_DLY] = REG_FIELD(0x09, 3, 3),
|
||||
[F_BATFET_RST_EN] = REG_FIELD(0x09, 2, 2),
|
||||
[F_PUMPX_UP] = REG_FIELD(0x09, 1, 1),
|
||||
[F_PUMPX_DN] = REG_FIELD(0x09, 0, 0),
|
||||
/* REG0A */
|
||||
[F_BOOSTV] = REG_FIELD(0x0A, 4, 7),
|
||||
[F_BOOSTI] = REG_FIELD(0x0A, 0, 2),
|
||||
/* REG0B */
|
||||
[F_VBUS_STAT] = REG_FIELD(0x0B, 5, 7),
|
||||
[F_CHG_STAT] = REG_FIELD(0x0B, 3, 4),
|
||||
[F_PG_STAT] = REG_FIELD(0x0B, 2, 2),
|
||||
[F_SDP_STAT] = REG_FIELD(0x0B, 1, 1),
|
||||
[F_VSYS_STAT] = REG_FIELD(0x0B, 0, 0),
|
||||
/* REG0C */
|
||||
[F_WD_FAULT] = REG_FIELD(0x0C, 7, 7),
|
||||
[F_BOOST_FAULT] = REG_FIELD(0x0C, 6, 6),
|
||||
[F_CHG_FAULT] = REG_FIELD(0x0C, 4, 5),
|
||||
[F_BAT_FAULT] = REG_FIELD(0x0C, 3, 3),
|
||||
[F_NTC_FAULT] = REG_FIELD(0x0C, 0, 2),
|
||||
/* REG0D */
|
||||
[F_FORCE_VINDPM] = REG_FIELD(0x0D, 7, 7),
|
||||
[F_VINDPM] = REG_FIELD(0x0D, 0, 6),
|
||||
/* REG0E */
|
||||
[F_THERM_STAT] = REG_FIELD(0x0E, 7, 7),
|
||||
[F_BATV] = REG_FIELD(0x0E, 0, 6),
|
||||
/* REG0F */
|
||||
[F_SYSV] = REG_FIELD(0x0F, 0, 6),
|
||||
/* REG10 */
|
||||
[F_TSPCT] = REG_FIELD(0x10, 0, 6),
|
||||
/* REG11 */
|
||||
[F_VBUS_GD] = REG_FIELD(0x11, 7, 7),
|
||||
[F_VBUSV] = REG_FIELD(0x11, 0, 6),
|
||||
/* REG12 */
|
||||
[F_ICHGR] = REG_FIELD(0x12, 0, 6),
|
||||
/* REG13 */
|
||||
[F_VDPM_STAT] = REG_FIELD(0x13, 7, 7),
|
||||
[F_IDPM_STAT] = REG_FIELD(0x13, 6, 6),
|
||||
[F_IDPM_LIM] = REG_FIELD(0x13, 0, 5),
|
||||
/* REG14 */
|
||||
[F_REG_RST] = REG_FIELD(0x14, 7, 7),
|
||||
[F_ICO_OPTIMIZED] = REG_FIELD(0x14, 6, 6),
|
||||
[F_PN] = REG_FIELD(0x14, 3, 5),
|
||||
[F_TS_PROFILE] = REG_FIELD(0x14, 2, 2),
|
||||
[F_DEV_REV] = REG_FIELD(0x14, 0, 1)
|
||||
};
|
||||
|
||||
/*
|
||||
* Most of the val -> idx conversions can be computed, given the minimum,
|
||||
* maximum and the step between values. For the rest of conversions, we use
|
||||
* lookup tables.
|
||||
*/
|
||||
enum bq25890_table_ids {
|
||||
/* range tables */
|
||||
TBL_ICHG,
|
||||
TBL_ITERM,
|
||||
TBL_IPRECHG,
|
||||
TBL_VREG,
|
||||
TBL_BATCMP,
|
||||
TBL_VCLAMP,
|
||||
TBL_BOOSTV,
|
||||
TBL_SYSVMIN,
|
||||
|
||||
/* lookup tables */
|
||||
TBL_TREG,
|
||||
TBL_BOOSTI,
|
||||
};
|
||||
|
||||
/* Thermal Regulation Threshold lookup table, in degrees Celsius */
|
||||
static const u32 bq25890_treg_tbl[] = { 60, 80, 100, 120 };
|
||||
|
||||
#define BQ25890_TREG_TBL_SIZE ARRAY_SIZE(bq25890_treg_tbl)
|
||||
|
||||
/* Boost mode current limit lookup table, in uA */
|
||||
static const u32 bq25890_boosti_tbl[] = {
|
||||
500000, 700000, 1100000, 1300000, 1600000, 1800000, 2100000, 2400000
|
||||
};
|
||||
|
||||
#define BQ25890_BOOSTI_TBL_SIZE ARRAY_SIZE(bq25890_boosti_tbl)
|
||||
|
||||
struct bq25890_range {
|
||||
u32 min;
|
||||
u32 max;
|
||||
u32 step;
|
||||
};
|
||||
|
||||
struct bq25890_lookup {
|
||||
const u32 *tbl;
|
||||
u32 size;
|
||||
};
|
||||
|
||||
static const union {
|
||||
struct bq25890_range rt;
|
||||
struct bq25890_lookup lt;
|
||||
} bq25890_tables[] = {
|
||||
/* range tables */
|
||||
[TBL_ICHG] = { .rt = {0, 5056000, 64000} }, /* uA */
|
||||
[TBL_ITERM] = { .rt = {64000, 1024000, 64000} }, /* uA */
|
||||
[TBL_VREG] = { .rt = {3840000, 4608000, 16000} }, /* uV */
|
||||
[TBL_BATCMP] = { .rt = {0, 140, 20} }, /* mOhm */
|
||||
[TBL_VCLAMP] = { .rt = {0, 224000, 32000} }, /* uV */
|
||||
[TBL_BOOSTV] = { .rt = {4550000, 5510000, 64000} }, /* uV */
|
||||
[TBL_SYSVMIN] = { .rt = {3000000, 3700000, 100000} }, /* uV */
|
||||
|
||||
/* lookup tables */
|
||||
[TBL_TREG] = { .lt = {bq25890_treg_tbl, BQ25890_TREG_TBL_SIZE} },
|
||||
[TBL_BOOSTI] = { .lt = {bq25890_boosti_tbl, BQ25890_BOOSTI_TBL_SIZE} }
|
||||
};
|
||||
|
||||
static int bq25890_field_read(struct bq25890_device *bq,
|
||||
enum bq25890_fields field_id)
|
||||
{
|
||||
int ret;
|
||||
int val;
|
||||
|
||||
ret = regmap_field_read(bq->rmap_fields[field_id], &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static int bq25890_field_write(struct bq25890_device *bq,
|
||||
enum bq25890_fields field_id, u8 val)
|
||||
{
|
||||
return regmap_field_write(bq->rmap_fields[field_id], val);
|
||||
}
|
||||
|
||||
static u8 bq25890_find_idx(u32 value, enum bq25890_table_ids id)
|
||||
{
|
||||
u8 idx;
|
||||
|
||||
if (id >= TBL_TREG) {
|
||||
const u32 *tbl = bq25890_tables[id].lt.tbl;
|
||||
u32 tbl_size = bq25890_tables[id].lt.size;
|
||||
|
||||
for (idx = 1; idx < tbl_size && tbl[idx] <= value; idx++)
|
||||
;
|
||||
} else {
|
||||
const struct bq25890_range *rtbl = &bq25890_tables[id].rt;
|
||||
u8 rtbl_size;
|
||||
|
||||
rtbl_size = (rtbl->max - rtbl->min) / rtbl->step + 1;
|
||||
|
||||
for (idx = 1;
|
||||
idx < rtbl_size && (idx * rtbl->step + rtbl->min <= value);
|
||||
idx++)
|
||||
;
|
||||
}
|
||||
|
||||
return idx - 1;
|
||||
}
|
||||
|
||||
static u32 bq25890_find_val(u8 idx, enum bq25890_table_ids id)
|
||||
{
|
||||
const struct bq25890_range *rtbl;
|
||||
|
||||
/* lookup table? */
|
||||
if (id >= TBL_TREG)
|
||||
return bq25890_tables[id].lt.tbl[idx];
|
||||
|
||||
/* range table */
|
||||
rtbl = &bq25890_tables[id].rt;
|
||||
|
||||
return (rtbl->min + idx * rtbl->step);
|
||||
}
|
||||
|
||||
enum bq25890_status {
|
||||
STATUS_NOT_CHARGING,
|
||||
STATUS_PRE_CHARGING,
|
||||
STATUS_FAST_CHARGING,
|
||||
STATUS_TERMINATION_DONE,
|
||||
};
|
||||
|
||||
enum bq25890_chrg_fault {
|
||||
CHRG_FAULT_NORMAL,
|
||||
CHRG_FAULT_INPUT,
|
||||
CHRG_FAULT_THERMAL_SHUTDOWN,
|
||||
CHRG_FAULT_TIMER_EXPIRED,
|
||||
};
|
||||
|
||||
static int bq25890_power_supply_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
int ret;
|
||||
struct bq25890_device *bq = power_supply_get_drvdata(psy);
|
||||
struct bq25890_state state;
|
||||
|
||||
mutex_lock(&bq->lock);
|
||||
state = bq->state;
|
||||
mutex_unlock(&bq->lock);
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
if (!state.online)
|
||||
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
else if (state.chrg_status == STATUS_NOT_CHARGING)
|
||||
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
||||
else if (state.chrg_status == STATUS_PRE_CHARGING ||
|
||||
state.chrg_status == STATUS_FAST_CHARGING)
|
||||
val->intval = POWER_SUPPLY_STATUS_CHARGING;
|
||||
else if (state.chrg_status == STATUS_TERMINATION_DONE)
|
||||
val->intval = POWER_SUPPLY_STATUS_FULL;
|
||||
else
|
||||
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
|
||||
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_MANUFACTURER:
|
||||
val->strval = BQ25890_MANUFACTURER;
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
val->intval = state.online;
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_HEALTH:
|
||||
if (!state.chrg_fault && !state.bat_fault && !state.boost_fault)
|
||||
val->intval = POWER_SUPPLY_HEALTH_GOOD;
|
||||
else if (state.bat_fault)
|
||||
val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
|
||||
else if (state.chrg_fault == CHRG_FAULT_TIMER_EXPIRED)
|
||||
val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
|
||||
else if (state.chrg_fault == CHRG_FAULT_THERMAL_SHUTDOWN)
|
||||
val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
|
||||
else
|
||||
val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
||||
ret = bq25890_field_read(bq, F_ICHGR); /* read measured value */
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* converted_val = ADC_val * 50mA (table 10.3.19) */
|
||||
val->intval = ret * 50000;
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
||||
val->intval = bq25890_tables[TBL_ICHG].rt.max;
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
|
||||
if (!state.online) {
|
||||
val->intval = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
ret = bq25890_field_read(bq, F_BATV); /* read measured value */
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* converted_val = 2.304V + ADC_val * 20mV (table 10.3.15) */
|
||||
val->intval = 2304000 + ret * 20000;
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
|
||||
val->intval = bq25890_tables[TBL_VREG].rt.max;
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
|
||||
val->intval = bq25890_find_val(bq->init_data.iterm, TBL_ITERM);
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bq25890_get_chip_state(struct bq25890_device *bq,
|
||||
struct bq25890_state *state)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
struct {
|
||||
enum bq25890_fields id;
|
||||
u8 *data;
|
||||
} state_fields[] = {
|
||||
{F_CHG_STAT, &state->chrg_status},
|
||||
{F_PG_STAT, &state->online},
|
||||
{F_VSYS_STAT, &state->vsys_status},
|
||||
{F_BOOST_FAULT, &state->boost_fault},
|
||||
{F_BAT_FAULT, &state->bat_fault},
|
||||
{F_CHG_FAULT, &state->chrg_fault}
|
||||
};
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(state_fields); i++) {
|
||||
ret = bq25890_field_read(bq, state_fields[i].id);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*state_fields[i].data = ret;
|
||||
}
|
||||
|
||||
dev_dbg(bq->dev, "S:CHG/PG/VSYS=%d/%d/%d, F:CHG/BOOST/BAT=%d/%d/%d\n",
|
||||
state->chrg_status, state->online, state->vsys_status,
|
||||
state->chrg_fault, state->boost_fault, state->bat_fault);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool bq25890_state_changed(struct bq25890_device *bq,
|
||||
struct bq25890_state *new_state)
|
||||
{
|
||||
struct bq25890_state old_state;
|
||||
|
||||
mutex_lock(&bq->lock);
|
||||
old_state = bq->state;
|
||||
mutex_unlock(&bq->lock);
|
||||
|
||||
return (old_state.chrg_status != new_state->chrg_status ||
|
||||
old_state.chrg_fault != new_state->chrg_fault ||
|
||||
old_state.online != new_state->online ||
|
||||
old_state.bat_fault != new_state->bat_fault ||
|
||||
old_state.boost_fault != new_state->boost_fault ||
|
||||
old_state.vsys_status != new_state->vsys_status);
|
||||
}
|
||||
|
||||
static void bq25890_handle_state_change(struct bq25890_device *bq,
|
||||
struct bq25890_state *new_state)
|
||||
{
|
||||
int ret;
|
||||
struct bq25890_state old_state;
|
||||
|
||||
mutex_lock(&bq->lock);
|
||||
old_state = bq->state;
|
||||
mutex_unlock(&bq->lock);
|
||||
|
||||
if (!new_state->online) { /* power removed */
|
||||
/* disable ADC */
|
||||
ret = bq25890_field_write(bq, F_CONV_START, 0);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
} else if (!old_state.online) { /* power inserted */
|
||||
/* enable ADC, to have control of charge current/voltage */
|
||||
ret = bq25890_field_write(bq, F_CONV_START, 1);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
dev_err(bq->dev, "Error communicating with the chip.\n");
|
||||
}
|
||||
|
||||
static irqreturn_t bq25890_irq_handler_thread(int irq, void *private)
|
||||
{
|
||||
struct bq25890_device *bq = private;
|
||||
int ret;
|
||||
struct bq25890_state state;
|
||||
|
||||
ret = bq25890_get_chip_state(bq, &state);
|
||||
if (ret < 0)
|
||||
goto handled;
|
||||
|
||||
if (!bq25890_state_changed(bq, &state))
|
||||
goto handled;
|
||||
|
||||
bq25890_handle_state_change(bq, &state);
|
||||
|
||||
mutex_lock(&bq->lock);
|
||||
bq->state = state;
|
||||
mutex_unlock(&bq->lock);
|
||||
|
||||
power_supply_changed(bq->charger);
|
||||
|
||||
handled:
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int bq25890_chip_reset(struct bq25890_device *bq)
|
||||
{
|
||||
int ret;
|
||||
int rst_check_counter = 10;
|
||||
|
||||
ret = bq25890_field_write(bq, F_REG_RST, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
do {
|
||||
ret = bq25890_field_read(bq, F_REG_RST);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
usleep_range(5, 10);
|
||||
} while (ret == 1 && --rst_check_counter);
|
||||
|
||||
if (!rst_check_counter)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bq25890_hw_init(struct bq25890_device *bq)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
struct bq25890_state state;
|
||||
|
||||
const struct {
|
||||
enum bq25890_fields id;
|
||||
u32 value;
|
||||
} init_data[] = {
|
||||
{F_ICHG, bq->init_data.ichg},
|
||||
{F_VREG, bq->init_data.vreg},
|
||||
{F_ITERM, bq->init_data.iterm},
|
||||
{F_IPRECHG, bq->init_data.iprechg},
|
||||
{F_SYSVMIN, bq->init_data.sysvmin},
|
||||
{F_BOOSTV, bq->init_data.boostv},
|
||||
{F_BOOSTI, bq->init_data.boosti},
|
||||
{F_BOOSTF, bq->init_data.boostf},
|
||||
{F_EN_ILIM, bq->init_data.ilim_en},
|
||||
{F_TREG, bq->init_data.treg}
|
||||
};
|
||||
|
||||
ret = bq25890_chip_reset(bq);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* disable watchdog */
|
||||
ret = bq25890_field_write(bq, F_WD, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* initialize currents/voltages and other parameters */
|
||||
for (i = 0; i < ARRAY_SIZE(init_data); i++) {
|
||||
ret = bq25890_field_write(bq, init_data[i].id,
|
||||
init_data[i].value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Configure ADC for continuous conversions. This does not enable it. */
|
||||
ret = bq25890_field_write(bq, F_CONV_RATE, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = bq25890_get_chip_state(bq, &state);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&bq->lock);
|
||||
bq->state = state;
|
||||
mutex_unlock(&bq->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum power_supply_property bq25890_power_supply_props[] = {
|
||||
POWER_SUPPLY_PROP_MANUFACTURER,
|
||||
POWER_SUPPLY_PROP_STATUS,
|
||||
POWER_SUPPLY_PROP_ONLINE,
|
||||
POWER_SUPPLY_PROP_HEALTH,
|
||||
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
|
||||
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
|
||||
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
|
||||
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
|
||||
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
|
||||
};
|
||||
|
||||
static char *bq25890_charger_supplied_to[] = {
|
||||
"main-battery",
|
||||
};
|
||||
|
||||
static const struct power_supply_desc bq25890_power_supply_desc = {
|
||||
.name = "bq25890-charger",
|
||||
.type = POWER_SUPPLY_TYPE_USB,
|
||||
.properties = bq25890_power_supply_props,
|
||||
.num_properties = ARRAY_SIZE(bq25890_power_supply_props),
|
||||
.get_property = bq25890_power_supply_get_property,
|
||||
};
|
||||
|
||||
static int bq25890_power_supply_init(struct bq25890_device *bq)
|
||||
{
|
||||
struct power_supply_config psy_cfg = { .drv_data = bq, };
|
||||
|
||||
psy_cfg.supplied_to = bq25890_charger_supplied_to;
|
||||
psy_cfg.num_supplicants = ARRAY_SIZE(bq25890_charger_supplied_to);
|
||||
|
||||
bq->charger = power_supply_register(bq->dev, &bq25890_power_supply_desc,
|
||||
&psy_cfg);
|
||||
|
||||
return PTR_ERR_OR_ZERO(bq->charger);
|
||||
}
|
||||
|
||||
static void bq25890_usb_work(struct work_struct *data)
|
||||
{
|
||||
int ret;
|
||||
struct bq25890_device *bq =
|
||||
container_of(data, struct bq25890_device, usb_work);
|
||||
|
||||
switch (bq->usb_event) {
|
||||
case USB_EVENT_ID:
|
||||
/* Enable boost mode */
|
||||
ret = bq25890_field_write(bq, F_OTG_CFG, 1);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
break;
|
||||
|
||||
case USB_EVENT_NONE:
|
||||
/* Disable boost mode */
|
||||
ret = bq25890_field_write(bq, F_OTG_CFG, 0);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
power_supply_changed(bq->charger);
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
dev_err(bq->dev, "Error switching to boost/charger mode.\n");
|
||||
}
|
||||
|
||||
static int bq25890_usb_notifier(struct notifier_block *nb, unsigned long val,
|
||||
void *priv)
|
||||
{
|
||||
struct bq25890_device *bq =
|
||||
container_of(nb, struct bq25890_device, usb_nb);
|
||||
|
||||
bq->usb_event = val;
|
||||
queue_work(system_power_efficient_wq, &bq->usb_work);
|
||||
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static int bq25890_irq_probe(struct bq25890_device *bq)
|
||||
{
|
||||
struct gpio_desc *irq;
|
||||
|
||||
irq = devm_gpiod_get_index(bq->dev, BQ25890_IRQ_PIN, 0, GPIOD_IN);
|
||||
if (IS_ERR(irq)) {
|
||||
dev_err(bq->dev, "Could not probe irq pin.\n");
|
||||
return PTR_ERR(irq);
|
||||
}
|
||||
|
||||
return gpiod_to_irq(irq);
|
||||
}
|
||||
|
||||
static int bq25890_fw_read_u32_props(struct bq25890_device *bq)
|
||||
{
|
||||
int ret;
|
||||
u32 property;
|
||||
int i;
|
||||
struct bq25890_init_data *init = &bq->init_data;
|
||||
struct {
|
||||
char *name;
|
||||
bool optional;
|
||||
enum bq25890_table_ids tbl_id;
|
||||
u8 *conv_data; /* holds converted value from given property */
|
||||
} props[] = {
|
||||
/* required properties */
|
||||
{"ti,charge-current", false, TBL_ICHG, &init->ichg},
|
||||
{"ti,battery-regulation-voltage", false, TBL_VREG, &init->vreg},
|
||||
{"ti,termination-current", false, TBL_ITERM, &init->iterm},
|
||||
{"ti,precharge-current", false, TBL_ITERM, &init->iprechg},
|
||||
{"ti,minimum-sys-voltage", false, TBL_SYSVMIN, &init->sysvmin},
|
||||
{"ti,boost-voltage", false, TBL_BOOSTV, &init->boostv},
|
||||
{"ti,boost-max-current", false, TBL_BOOSTI, &init->boosti},
|
||||
|
||||
/* optional properties */
|
||||
{"ti,thermal-regulation-threshold", true, TBL_TREG, &init->treg}
|
||||
};
|
||||
|
||||
/* initialize data for optional properties */
|
||||
init->treg = 3; /* 120 degrees Celsius */
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(props); i++) {
|
||||
ret = device_property_read_u32(bq->dev, props[i].name,
|
||||
&property);
|
||||
if (ret < 0) {
|
||||
if (props[i].optional)
|
||||
continue;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
*props[i].conv_data = bq25890_find_idx(property,
|
||||
props[i].tbl_id);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bq25890_fw_probe(struct bq25890_device *bq)
|
||||
{
|
||||
int ret;
|
||||
struct bq25890_init_data *init = &bq->init_data;
|
||||
|
||||
ret = bq25890_fw_read_u32_props(bq);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
init->ilim_en = device_property_read_bool(bq->dev, "ti,use-ilim-pin");
|
||||
init->boostf = device_property_read_bool(bq->dev, "ti,boost-low-freq");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bq25890_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
|
||||
struct device *dev = &client->dev;
|
||||
struct bq25890_device *bq;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
|
||||
dev_err(dev, "No support for SMBUS_BYTE_DATA\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
bq = devm_kzalloc(dev, sizeof(*bq), GFP_KERNEL);
|
||||
if (!bq)
|
||||
return -ENOMEM;
|
||||
|
||||
bq->client = client;
|
||||
bq->dev = dev;
|
||||
|
||||
mutex_init(&bq->lock);
|
||||
|
||||
bq->rmap = devm_regmap_init_i2c(client, &bq25890_regmap_config);
|
||||
if (IS_ERR(bq->rmap)) {
|
||||
dev_err(dev, "failed to allocate register map\n");
|
||||
return PTR_ERR(bq->rmap);
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(bq25890_reg_fields); i++) {
|
||||
const struct reg_field *reg_fields = bq25890_reg_fields;
|
||||
|
||||
bq->rmap_fields[i] = devm_regmap_field_alloc(dev, bq->rmap,
|
||||
reg_fields[i]);
|
||||
if (IS_ERR(bq->rmap_fields[i])) {
|
||||
dev_err(dev, "cannot allocate regmap field\n");
|
||||
return PTR_ERR(bq->rmap_fields[i]);
|
||||
}
|
||||
}
|
||||
|
||||
i2c_set_clientdata(client, bq);
|
||||
|
||||
bq->chip_id = bq25890_field_read(bq, F_PN);
|
||||
if (bq->chip_id < 0) {
|
||||
dev_err(dev, "Cannot read chip ID.\n");
|
||||
return bq->chip_id;
|
||||
}
|
||||
|
||||
if (bq->chip_id != BQ25890_ID) {
|
||||
dev_err(dev, "Chip with ID=%d, not supported!\n", bq->chip_id);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (!dev->platform_data) {
|
||||
ret = bq25890_fw_probe(bq);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Cannot read device properties.\n");
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = bq25890_hw_init(bq);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Cannot initialize the chip.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (client->irq <= 0)
|
||||
client->irq = bq25890_irq_probe(bq);
|
||||
|
||||
if (client->irq < 0) {
|
||||
dev_err(dev, "No irq resource found.\n");
|
||||
return client->irq;
|
||||
}
|
||||
|
||||
/* OTG reporting */
|
||||
bq->usb_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2);
|
||||
if (!IS_ERR_OR_NULL(bq->usb_phy)) {
|
||||
INIT_WORK(&bq->usb_work, bq25890_usb_work);
|
||||
bq->usb_nb.notifier_call = bq25890_usb_notifier;
|
||||
usb_register_notifier(bq->usb_phy, &bq->usb_nb);
|
||||
}
|
||||
|
||||
ret = devm_request_threaded_irq(dev, client->irq, NULL,
|
||||
bq25890_irq_handler_thread,
|
||||
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||||
BQ25890_IRQ_PIN, bq);
|
||||
if (ret)
|
||||
goto irq_fail;
|
||||
|
||||
ret = bq25890_power_supply_init(bq);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to register power supply\n");
|
||||
goto irq_fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
irq_fail:
|
||||
if (!IS_ERR_OR_NULL(bq->usb_phy))
|
||||
usb_unregister_notifier(bq->usb_phy, &bq->usb_nb);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bq25890_remove(struct i2c_client *client)
|
||||
{
|
||||
struct bq25890_device *bq = i2c_get_clientdata(client);
|
||||
|
||||
power_supply_unregister(bq->charger);
|
||||
|
||||
if (!IS_ERR_OR_NULL(bq->usb_phy))
|
||||
usb_unregister_notifier(bq->usb_phy, &bq->usb_nb);
|
||||
|
||||
/* reset all registers to default values */
|
||||
bq25890_chip_reset(bq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int bq25890_suspend(struct device *dev)
|
||||
{
|
||||
struct bq25890_device *bq = dev_get_drvdata(dev);
|
||||
|
||||
/*
|
||||
* If charger is removed, while in suspend, make sure ADC is diabled
|
||||
* since it consumes slightly more power.
|
||||
*/
|
||||
return bq25890_field_write(bq, F_CONV_START, 0);
|
||||
}
|
||||
|
||||
static int bq25890_resume(struct device *dev)
|
||||
{
|
||||
int ret;
|
||||
struct bq25890_state state;
|
||||
struct bq25890_device *bq = dev_get_drvdata(dev);
|
||||
|
||||
ret = bq25890_get_chip_state(bq, &state);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&bq->lock);
|
||||
bq->state = state;
|
||||
mutex_unlock(&bq->lock);
|
||||
|
||||
/* Re-enable ADC only if charger is plugged in. */
|
||||
if (state.online) {
|
||||
ret = bq25890_field_write(bq, F_CONV_START, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* signal userspace, maybe state changed while suspended */
|
||||
power_supply_changed(bq->charger);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops bq25890_pm = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(bq25890_suspend, bq25890_resume)
|
||||
};
|
||||
|
||||
static const struct i2c_device_id bq25890_i2c_ids[] = {
|
||||
{ "bq25890", 0 },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, bq25890_i2c_ids);
|
||||
|
||||
static const struct of_device_id bq25890_of_match[] = {
|
||||
{ .compatible = "ti,bq25890", },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, bq25890_of_match);
|
||||
|
||||
static const struct acpi_device_id bq25890_acpi_match[] = {
|
||||
{"BQ258900", 0},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, bq25890_acpi_match);
|
||||
|
||||
static struct i2c_driver bq25890_driver = {
|
||||
.driver = {
|
||||
.name = "bq25890-charger",
|
||||
.of_match_table = of_match_ptr(bq25890_of_match),
|
||||
.acpi_match_table = ACPI_PTR(bq25890_acpi_match),
|
||||
.pm = &bq25890_pm,
|
||||
},
|
||||
.probe = bq25890_probe,
|
||||
.remove = bq25890_remove,
|
||||
.id_table = bq25890_i2c_ids,
|
||||
};
|
||||
module_i2c_driver(bq25890_driver);
|
||||
|
||||
MODULE_AUTHOR("Laurentiu Palcu <laurentiu.palcu@intel.com>");
|
||||
MODULE_DESCRIPTION("bq25890 charger driver");
|
||||
MODULE_LICENSE("GPL");
|
@ -1768,7 +1768,8 @@ static int charger_manager_probe(struct platform_device *pdev)
|
||||
|
||||
INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk);
|
||||
|
||||
cm->charger_psy = power_supply_register(NULL, &cm->charger_psy_desc,
|
||||
cm->charger_psy = power_supply_register(&pdev->dev,
|
||||
&cm->charger_psy_desc,
|
||||
&psy_cfg);
|
||||
if (IS_ERR(cm->charger_psy)) {
|
||||
dev_err(&pdev->dev, "Cannot register charger-manager with name \"%s\"\n",
|
||||
|
@ -63,6 +63,8 @@
|
||||
#define dP_ACC_100 0x1900
|
||||
#define dP_ACC_200 0x3200
|
||||
|
||||
#define MAX17042_VMAX_TOLERANCE 50 /* 50 mV */
|
||||
|
||||
struct max17042_chip {
|
||||
struct i2c_client *client;
|
||||
struct regmap *regmap;
|
||||
@ -85,10 +87,94 @@ static enum power_supply_property max17042_battery_props[] = {
|
||||
POWER_SUPPLY_PROP_CHARGE_FULL,
|
||||
POWER_SUPPLY_PROP_CHARGE_COUNTER,
|
||||
POWER_SUPPLY_PROP_TEMP,
|
||||
POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
|
||||
POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
|
||||
POWER_SUPPLY_PROP_TEMP_MIN,
|
||||
POWER_SUPPLY_PROP_TEMP_MAX,
|
||||
POWER_SUPPLY_PROP_HEALTH,
|
||||
POWER_SUPPLY_PROP_CURRENT_NOW,
|
||||
POWER_SUPPLY_PROP_CURRENT_AVG,
|
||||
};
|
||||
|
||||
static int max17042_get_temperature(struct max17042_chip *chip, int *temp)
|
||||
{
|
||||
int ret;
|
||||
u32 data;
|
||||
struct regmap *map = chip->regmap;
|
||||
|
||||
ret = regmap_read(map, MAX17042_TEMP, &data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*temp = data;
|
||||
/* The value is signed. */
|
||||
if (*temp & 0x8000) {
|
||||
*temp = (0x7fff & ~*temp) + 1;
|
||||
*temp *= -1;
|
||||
}
|
||||
|
||||
/* The value is converted into deci-centigrade scale */
|
||||
/* Units of LSB = 1 / 256 degree Celsius */
|
||||
*temp = *temp * 10 / 256;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max17042_get_battery_health(struct max17042_chip *chip, int *health)
|
||||
{
|
||||
int temp, vavg, vbatt, ret;
|
||||
u32 val;
|
||||
|
||||
ret = regmap_read(chip->regmap, MAX17042_AvgVCELL, &val);
|
||||
if (ret < 0)
|
||||
goto health_error;
|
||||
|
||||
/* bits [0-3] unused */
|
||||
vavg = val * 625 / 8;
|
||||
/* Convert to millivolts */
|
||||
vavg /= 1000;
|
||||
|
||||
ret = regmap_read(chip->regmap, MAX17042_VCELL, &val);
|
||||
if (ret < 0)
|
||||
goto health_error;
|
||||
|
||||
/* bits [0-3] unused */
|
||||
vbatt = val * 625 / 8;
|
||||
/* Convert to millivolts */
|
||||
vbatt /= 1000;
|
||||
|
||||
if (vavg < chip->pdata->vmin) {
|
||||
*health = POWER_SUPPLY_HEALTH_DEAD;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (vbatt > chip->pdata->vmax + MAX17042_VMAX_TOLERANCE) {
|
||||
*health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = max17042_get_temperature(chip, &temp);
|
||||
if (ret < 0)
|
||||
goto health_error;
|
||||
|
||||
if (temp <= chip->pdata->temp_min) {
|
||||
*health = POWER_SUPPLY_HEALTH_COLD;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (temp >= chip->pdata->temp_max) {
|
||||
*health = POWER_SUPPLY_HEALTH_OVERHEAT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
*health = POWER_SUPPLY_HEALTH_GOOD;
|
||||
|
||||
out:
|
||||
return 0;
|
||||
|
||||
health_error:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int max17042_get_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
@ -181,19 +267,34 @@ static int max17042_get_property(struct power_supply *psy,
|
||||
val->intval = data * 1000 / 2;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TEMP:
|
||||
ret = regmap_read(map, MAX17042_TEMP, &data);
|
||||
ret = max17042_get_temperature(chip, &val->intval);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
|
||||
ret = regmap_read(map, MAX17042_TALRT_Th, &data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
/* LSB is Alert Minimum. In deci-centigrade */
|
||||
val->intval = (data & 0xff) * 10;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
|
||||
ret = regmap_read(map, MAX17042_TALRT_Th, &data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
/* MSB is Alert Maximum. In deci-centigrade */
|
||||
val->intval = (data >> 8) * 10;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TEMP_MIN:
|
||||
val->intval = chip->pdata->temp_min;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TEMP_MAX:
|
||||
val->intval = chip->pdata->temp_max;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_HEALTH:
|
||||
ret = max17042_get_battery_health(chip, &val->intval);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
val->intval = data;
|
||||
/* The value is signed. */
|
||||
if (val->intval & 0x8000) {
|
||||
val->intval = (0x7fff & ~val->intval) + 1;
|
||||
val->intval *= -1;
|
||||
}
|
||||
/* The value is converted into deci-centigrade scale */
|
||||
/* Units of LSB = 1 / 256 degree Celsius */
|
||||
val->intval = val->intval * 10 / 256;
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
||||
if (chip->pdata->enable_current_sense) {
|
||||
@ -237,6 +338,69 @@ static int max17042_get_property(struct power_supply *psy,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max17042_set_property(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
const union power_supply_propval *val)
|
||||
{
|
||||
struct max17042_chip *chip = power_supply_get_drvdata(psy);
|
||||
struct regmap *map = chip->regmap;
|
||||
int ret = 0;
|
||||
u32 data;
|
||||
int8_t temp;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
|
||||
ret = regmap_read(map, MAX17042_TALRT_Th, &data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Input in deci-centigrade, convert to centigrade */
|
||||
temp = val->intval / 10;
|
||||
/* force min < max */
|
||||
if (temp >= (int8_t)(data >> 8))
|
||||
temp = (int8_t)(data >> 8) - 1;
|
||||
/* Write both MAX and MIN ALERT */
|
||||
data = (data & 0xff00) + temp;
|
||||
ret = regmap_write(map, MAX17042_TALRT_Th, data);
|
||||
break;
|
||||
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
|
||||
ret = regmap_read(map, MAX17042_TALRT_Th, &data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Input in Deci-Centigrade, convert to centigrade */
|
||||
temp = val->intval / 10;
|
||||
/* force max > min */
|
||||
if (temp <= (int8_t)(data & 0xff))
|
||||
temp = (int8_t)(data & 0xff) + 1;
|
||||
/* Write both MAX and MIN ALERT */
|
||||
data = (data & 0xff) + (temp << 8);
|
||||
ret = regmap_write(map, MAX17042_TALRT_Th, data);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int max17042_property_is_writeable(struct power_supply *psy,
|
||||
enum power_supply_property psp)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
|
||||
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
|
||||
ret = 1;
|
||||
break;
|
||||
default:
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int max17042_write_verify_reg(struct regmap *map, u8 reg, u32 value)
|
||||
{
|
||||
int retries = 8;
|
||||
@ -645,6 +809,15 @@ max17042_get_pdata(struct device *dev)
|
||||
pdata->enable_current_sense = true;
|
||||
}
|
||||
|
||||
if (of_property_read_s32(np, "maxim,cold-temp", &pdata->temp_min))
|
||||
pdata->temp_min = INT_MIN;
|
||||
if (of_property_read_s32(np, "maxim,over-heat-temp", &pdata->temp_max))
|
||||
pdata->temp_max = INT_MAX;
|
||||
if (of_property_read_s32(np, "maxim,dead-volt", &pdata->vmin))
|
||||
pdata->vmin = INT_MIN;
|
||||
if (of_property_read_s32(np, "maxim,over-volt", &pdata->vmax))
|
||||
pdata->vmax = INT_MAX;
|
||||
|
||||
return pdata;
|
||||
}
|
||||
#else
|
||||
@ -665,6 +838,8 @@ static const struct power_supply_desc max17042_psy_desc = {
|
||||
.name = "max170xx_battery",
|
||||
.type = POWER_SUPPLY_TYPE_BATTERY,
|
||||
.get_property = max17042_get_property,
|
||||
.set_property = max17042_set_property,
|
||||
.property_is_writeable = max17042_property_is_writeable,
|
||||
.properties = max17042_battery_props,
|
||||
.num_properties = ARRAY_SIZE(max17042_battery_props),
|
||||
};
|
||||
@ -673,6 +848,8 @@ static const struct power_supply_desc max17042_no_current_sense_psy_desc = {
|
||||
.name = "max170xx_battery",
|
||||
.type = POWER_SUPPLY_TYPE_BATTERY,
|
||||
.get_property = max17042_get_property,
|
||||
.set_property = max17042_set_property,
|
||||
.property_is_writeable = max17042_property_is_writeable,
|
||||
.properties = max17042_battery_props,
|
||||
.num_properties = ARRAY_SIZE(max17042_battery_props) - 2,
|
||||
};
|
||||
|
@ -30,6 +30,8 @@ EXPORT_SYMBOL_GPL(power_supply_notifier);
|
||||
|
||||
static struct device_type power_supply_dev_type;
|
||||
|
||||
#define POWER_SUPPLY_DEFERRED_REGISTER_TIME msecs_to_jiffies(10)
|
||||
|
||||
static bool __power_supply_is_supplied_by(struct power_supply *supplier,
|
||||
struct power_supply *supply)
|
||||
{
|
||||
@ -121,6 +123,30 @@ void power_supply_changed(struct power_supply *psy)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(power_supply_changed);
|
||||
|
||||
/*
|
||||
* Notify that power supply was registered after parent finished the probing.
|
||||
*
|
||||
* Often power supply is registered from driver's probe function. However
|
||||
* calling power_supply_changed() directly from power_supply_register()
|
||||
* would lead to execution of get_property() function provided by the driver
|
||||
* too early - before the probe ends.
|
||||
*
|
||||
* Avoid that by waiting on parent's mutex.
|
||||
*/
|
||||
static void power_supply_deferred_register_work(struct work_struct *work)
|
||||
{
|
||||
struct power_supply *psy = container_of(work, struct power_supply,
|
||||
deferred_register_work.work);
|
||||
|
||||
if (psy->dev.parent)
|
||||
mutex_lock(&psy->dev.parent->mutex);
|
||||
|
||||
power_supply_changed(psy);
|
||||
|
||||
if (psy->dev.parent)
|
||||
mutex_unlock(&psy->dev.parent->mutex);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
#include <linux/of.h>
|
||||
|
||||
@ -420,6 +446,45 @@ struct power_supply *power_supply_get_by_phandle(struct device_node *np,
|
||||
return psy;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(power_supply_get_by_phandle);
|
||||
|
||||
static void devm_power_supply_put(struct device *dev, void *res)
|
||||
{
|
||||
struct power_supply **psy = res;
|
||||
|
||||
power_supply_put(*psy);
|
||||
}
|
||||
|
||||
/**
|
||||
* devm_power_supply_get_by_phandle() - Resource managed version of
|
||||
* power_supply_get_by_phandle()
|
||||
* @dev: Pointer to device holding phandle property
|
||||
* @phandle_name: Name of property holding a power supply phandle
|
||||
*
|
||||
* Return: On success returns a reference to a power supply with
|
||||
* matching name equals to value under @property, NULL or ERR_PTR otherwise.
|
||||
*/
|
||||
struct power_supply *devm_power_supply_get_by_phandle(struct device *dev,
|
||||
const char *property)
|
||||
{
|
||||
struct power_supply **ptr, *psy;
|
||||
|
||||
if (!dev->of_node)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
ptr = devres_alloc(devm_power_supply_put, sizeof(*ptr), GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
psy = power_supply_get_by_phandle(dev->of_node, property);
|
||||
if (IS_ERR_OR_NULL(psy)) {
|
||||
devres_free(ptr);
|
||||
} else {
|
||||
*ptr = psy;
|
||||
devres_add(dev, ptr);
|
||||
}
|
||||
return psy;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle);
|
||||
#endif /* CONFIG_OF */
|
||||
|
||||
int power_supply_get_property(struct power_supply *psy,
|
||||
@ -645,6 +710,10 @@ __power_supply_register(struct device *parent,
|
||||
struct power_supply *psy;
|
||||
int rc;
|
||||
|
||||
if (!parent)
|
||||
pr_warn("%s: Expected proper parent device for '%s'\n",
|
||||
__func__, desc->name);
|
||||
|
||||
psy = kzalloc(sizeof(*psy), GFP_KERNEL);
|
||||
if (!psy)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
@ -659,7 +728,6 @@ __power_supply_register(struct device *parent,
|
||||
dev->release = power_supply_dev_release;
|
||||
dev_set_drvdata(dev, psy);
|
||||
psy->desc = desc;
|
||||
atomic_inc(&psy->use_cnt);
|
||||
if (cfg) {
|
||||
psy->drv_data = cfg->drv_data;
|
||||
psy->of_node = cfg->of_node;
|
||||
@ -672,6 +740,8 @@ __power_supply_register(struct device *parent,
|
||||
goto dev_set_name_failed;
|
||||
|
||||
INIT_WORK(&psy->changed_work, power_supply_changed_work);
|
||||
INIT_DELAYED_WORK(&psy->deferred_register_work,
|
||||
power_supply_deferred_register_work);
|
||||
|
||||
rc = power_supply_check_supplies(psy);
|
||||
if (rc) {
|
||||
@ -700,7 +770,20 @@ __power_supply_register(struct device *parent,
|
||||
if (rc)
|
||||
goto create_triggers_failed;
|
||||
|
||||
power_supply_changed(psy);
|
||||
/*
|
||||
* Update use_cnt after any uevents (most notably from device_add()).
|
||||
* We are here still during driver's probe but
|
||||
* the power_supply_uevent() calls back driver's get_property
|
||||
* method so:
|
||||
* 1. Driver did not assigned the returned struct power_supply,
|
||||
* 2. Driver could not finish initialization (anything in its probe
|
||||
* after calling power_supply_register()).
|
||||
*/
|
||||
atomic_inc(&psy->use_cnt);
|
||||
|
||||
queue_delayed_work(system_power_efficient_wq,
|
||||
&psy->deferred_register_work,
|
||||
POWER_SUPPLY_DEFERRED_REGISTER_TIME);
|
||||
|
||||
return psy;
|
||||
|
||||
@ -720,7 +803,8 @@ dev_set_name_failed:
|
||||
|
||||
/**
|
||||
* power_supply_register() - Register new power supply
|
||||
* @parent: Device to be a parent of power supply's device
|
||||
* @parent: Device to be a parent of power supply's device, usually
|
||||
* the device which probe function calls this
|
||||
* @desc: Description of power supply, must be valid through whole
|
||||
* lifetime of this power supply
|
||||
* @cfg: Run-time specific configuration accessed during registering,
|
||||
@ -740,8 +824,9 @@ struct power_supply *__must_check power_supply_register(struct device *parent,
|
||||
EXPORT_SYMBOL_GPL(power_supply_register);
|
||||
|
||||
/**
|
||||
* power_supply_register() - Register new non-waking-source power supply
|
||||
* @parent: Device to be a parent of power supply's device
|
||||
* power_supply_register_no_ws() - Register new non-waking-source power supply
|
||||
* @parent: Device to be a parent of power supply's device, usually
|
||||
* the device which probe function calls this
|
||||
* @desc: Description of power supply, must be valid through whole
|
||||
* lifetime of this power supply
|
||||
* @cfg: Run-time specific configuration accessed during registering,
|
||||
@ -769,8 +854,9 @@ static void devm_power_supply_release(struct device *dev, void *res)
|
||||
}
|
||||
|
||||
/**
|
||||
* power_supply_register() - Register managed power supply
|
||||
* @parent: Device to be a parent of power supply's device
|
||||
* devm_power_supply_register() - Register managed power supply
|
||||
* @parent: Device to be a parent of power supply's device, usually
|
||||
* the device which probe function calls this
|
||||
* @desc: Description of power supply, must be valid through whole
|
||||
* lifetime of this power supply
|
||||
* @cfg: Run-time specific configuration accessed during registering,
|
||||
@ -804,8 +890,9 @@ devm_power_supply_register(struct device *parent,
|
||||
EXPORT_SYMBOL_GPL(devm_power_supply_register);
|
||||
|
||||
/**
|
||||
* power_supply_register() - Register managed non-waking-source power supply
|
||||
* @parent: Device to be a parent of power supply's device
|
||||
* devm_power_supply_register_no_ws() - Register managed non-waking-source power supply
|
||||
* @parent: Device to be a parent of power supply's device, usually
|
||||
* the device which probe function calls this
|
||||
* @desc: Description of power supply, must be valid through whole
|
||||
* lifetime of this power supply
|
||||
* @cfg: Run-time specific configuration accessed during registering,
|
||||
@ -849,6 +936,7 @@ void power_supply_unregister(struct power_supply *psy)
|
||||
{
|
||||
WARN_ON(atomic_dec_return(&psy->use_cnt));
|
||||
cancel_work_sync(&psy->changed_work);
|
||||
cancel_delayed_work_sync(&psy->deferred_register_work);
|
||||
sysfs_remove_link(&psy->dev.kobj, "powers");
|
||||
power_supply_remove_triggers(psy);
|
||||
psy_unregister_cooler(psy);
|
||||
|
@ -25,7 +25,7 @@ static void power_supply_update_bat_leds(struct power_supply *psy)
|
||||
unsigned long delay_on = 0;
|
||||
unsigned long delay_off = 0;
|
||||
|
||||
if (psy->desc->get_property(psy, POWER_SUPPLY_PROP_STATUS, &status))
|
||||
if (power_supply_get_property(psy, POWER_SUPPLY_PROP_STATUS, &status))
|
||||
return;
|
||||
|
||||
dev_dbg(&psy->dev, "%s %d\n", __func__, status.intval);
|
||||
@ -115,7 +115,7 @@ static void power_supply_update_gen_leds(struct power_supply *psy)
|
||||
{
|
||||
union power_supply_propval online;
|
||||
|
||||
if (psy->desc->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &online))
|
||||
if (power_supply_get_property(psy, POWER_SUPPLY_PROP_ONLINE, &online))
|
||||
return;
|
||||
|
||||
dev_dbg(&psy->dev, "%s %d\n", __func__, online.intval);
|
||||
|
@ -125,7 +125,7 @@ static ssize_t power_supply_store_property(struct device *dev,
|
||||
|
||||
value.intval = long_val;
|
||||
|
||||
ret = psy->desc->set_property(psy, off, &value);
|
||||
ret = power_supply_set_property(psy, off, &value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
@ -223,7 +223,7 @@ static umode_t power_supply_attr_is_visible(struct kobject *kobj,
|
||||
|
||||
if (property == attrno) {
|
||||
if (psy->desc->property_is_writeable &&
|
||||
power_supply_property_is_writeable(psy, property) > 0)
|
||||
psy->desc->property_is_writeable(psy, property) > 0)
|
||||
mode |= S_IWUSR;
|
||||
|
||||
return mode;
|
||||
|
@ -243,7 +243,7 @@ static int at91_reset_probe(struct platform_device *pdev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_device_id at91_reset_plat_match[] = {
|
||||
static const struct platform_device_id at91_reset_plat_match[] = {
|
||||
{ "at91-sam9260-reset", (unsigned long)at91sam9260_restart },
|
||||
{ "at91-sam9g45-reset", (unsigned long)at91sam9g45_restart },
|
||||
{ /* sentinel */ }
|
||||
|
@ -48,6 +48,7 @@ static void gpio_poweroff_do_poweroff(void)
|
||||
static int gpio_poweroff_probe(struct platform_device *pdev)
|
||||
{
|
||||
bool input = false;
|
||||
enum gpiod_flags flags;
|
||||
|
||||
/* If a pm_power_off function has already been added, leave it alone */
|
||||
if (pm_power_off != NULL) {
|
||||
@ -57,26 +58,16 @@ static int gpio_poweroff_probe(struct platform_device *pdev)
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
reset_gpio = devm_gpiod_get(&pdev->dev, NULL);
|
||||
input = of_property_read_bool(pdev->dev.of_node, "input");
|
||||
if (input)
|
||||
flags = GPIOD_IN;
|
||||
else
|
||||
flags = GPIOD_OUT_LOW;
|
||||
|
||||
reset_gpio = devm_gpiod_get(&pdev->dev, NULL, flags);
|
||||
if (IS_ERR(reset_gpio))
|
||||
return PTR_ERR(reset_gpio);
|
||||
|
||||
input = of_property_read_bool(pdev->dev.of_node, "input");
|
||||
|
||||
if (input) {
|
||||
if (gpiod_direction_input(reset_gpio)) {
|
||||
dev_err(&pdev->dev,
|
||||
"Could not set direction of reset GPIO to input\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
} else {
|
||||
if (gpiod_direction_output(reset_gpio, 0)) {
|
||||
dev_err(&pdev->dev,
|
||||
"Could not set direction of reset GPIO\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
pm_power_off = &gpio_poweroff_do_poweroff;
|
||||
return 0;
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ static int gpio_restart_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
gpio_restart->restart_handler.notifier_call = gpio_restart_notify;
|
||||
gpio_restart->restart_handler.priority = 128;
|
||||
gpio_restart->restart_handler.priority = 129;
|
||||
gpio_restart->active_delay_ms = 100;
|
||||
gpio_restart->inactive_delay_ms = 100;
|
||||
gpio_restart->wait_delay_ms = 3000;
|
||||
|
@ -201,16 +201,15 @@ static int ltc2952_poweroff_init(struct platform_device *pdev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
data->gpio_trigger = devm_gpiod_get(&pdev->dev, "trigger", GPIOD_IN);
|
||||
data->gpio_trigger = devm_gpiod_get_optional(&pdev->dev, "trigger",
|
||||
GPIOD_IN);
|
||||
if (IS_ERR(data->gpio_trigger)) {
|
||||
/*
|
||||
* It's not a problem if the trigger gpio isn't available, but
|
||||
* it is worth a warning if its use was defined in the device
|
||||
* tree.
|
||||
*/
|
||||
if (PTR_ERR(data->gpio_trigger) != -ENOENT)
|
||||
dev_err(&pdev->dev,
|
||||
"unable to claim gpio \"trigger\"\n");
|
||||
dev_err(&pdev->dev, "unable to claim gpio \"trigger\"\n");
|
||||
data->gpio_trigger = NULL;
|
||||
}
|
||||
|
||||
|
1752
drivers/power/rt9455_charger.c
Normal file
1752
drivers/power/rt9455_charger.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -28,6 +28,7 @@
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/stat.h>
|
||||
|
||||
#include <linux/power/sbs-battery.h>
|
||||
|
||||
@ -170,6 +171,7 @@ struct sbs_info {
|
||||
|
||||
static char model_name[I2C_SMBUS_BLOCK_MAX + 1];
|
||||
static char manufacturer[I2C_SMBUS_BLOCK_MAX + 1];
|
||||
static bool force_load;
|
||||
|
||||
static int sbs_read_word_data(struct i2c_client *client, u8 address)
|
||||
{
|
||||
@ -885,14 +887,17 @@ static int sbs_probe(struct i2c_client *client,
|
||||
|
||||
skip_gpio:
|
||||
/*
|
||||
* Before we register, we need to make sure we can actually talk
|
||||
* Before we register, we might need to make sure we can actually talk
|
||||
* to the battery.
|
||||
*/
|
||||
rc = sbs_read_word_data(client, sbs_data[REG_STATUS].addr);
|
||||
if (rc < 0) {
|
||||
dev_err(&client->dev, "%s: Failed to get device status\n",
|
||||
__func__);
|
||||
goto exit_psupply;
|
||||
if (!force_load) {
|
||||
rc = sbs_read_word_data(client, sbs_data[REG_STATUS].addr);
|
||||
|
||||
if (rc < 0) {
|
||||
dev_err(&client->dev, "%s: Failed to get device status\n",
|
||||
__func__);
|
||||
goto exit_psupply;
|
||||
}
|
||||
}
|
||||
|
||||
chip->power_supply = power_supply_register(&client->dev, sbs_desc,
|
||||
@ -991,3 +996,7 @@ module_i2c_driver(sbs_battery_driver);
|
||||
|
||||
MODULE_DESCRIPTION("SBS battery monitor driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_param(force_load, bool, S_IRUSR | S_IRGRP | S_IROTH);
|
||||
MODULE_PARM_DESC(force_load,
|
||||
"Attempt to load the driver even if no battery is connected");
|
||||
|
@ -609,6 +609,7 @@ static int wm831x_power_probe(struct platform_device *pdev)
|
||||
return ret;
|
||||
|
||||
err_bat_irq:
|
||||
--i;
|
||||
for (; i >= 0; i--) {
|
||||
irq = platform_get_irq_byname(pdev, wm831x_bat_irqs[i]);
|
||||
free_irq(irq, power);
|
||||
|
@ -275,4 +275,11 @@ struct axp20x_fg_pdata {
|
||||
int thermistor_curve[MAX_THERM_CURVE_SIZE][2];
|
||||
};
|
||||
|
||||
struct axp20x_chrg_pdata {
|
||||
int max_cc;
|
||||
int max_cv;
|
||||
int def_cc;
|
||||
int def_cv;
|
||||
};
|
||||
|
||||
#endif /* __LINUX_MFD_AXP20X_H */
|
||||
|
@ -215,6 +215,10 @@ struct max17042_platform_data {
|
||||
* the datasheet although it can be changed by board designers.
|
||||
*/
|
||||
unsigned int r_sns;
|
||||
int vmin; /* in millivolts */
|
||||
int vmax; /* in millivolts */
|
||||
int temp_min; /* in tenths of degree Celsius */
|
||||
int temp_max; /* in tenths of degree Celsius */
|
||||
};
|
||||
|
||||
#endif /* __MAX17042_BATTERY_H_ */
|
||||
|
@ -206,6 +206,11 @@ struct power_supply_desc {
|
||||
int (*set_property)(struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
const union power_supply_propval *val);
|
||||
/*
|
||||
* property_is_writeable() will be called during registration
|
||||
* of power supply. If this happens during device probe then it must
|
||||
* not access internal data of device (because probe did not end).
|
||||
*/
|
||||
int (*property_is_writeable)(struct power_supply *psy,
|
||||
enum power_supply_property psp);
|
||||
void (*external_power_changed)(struct power_supply *psy);
|
||||
@ -237,6 +242,7 @@ struct power_supply {
|
||||
/* private */
|
||||
struct device dev;
|
||||
struct work_struct changed_work;
|
||||
struct delayed_work deferred_register_work;
|
||||
spinlock_t changed_lock;
|
||||
bool changed;
|
||||
atomic_t use_cnt;
|
||||
@ -286,10 +292,15 @@ extern void power_supply_put(struct power_supply *psy);
|
||||
#ifdef CONFIG_OF
|
||||
extern struct power_supply *power_supply_get_by_phandle(struct device_node *np,
|
||||
const char *property);
|
||||
extern struct power_supply *devm_power_supply_get_by_phandle(
|
||||
struct device *dev, const char *property);
|
||||
#else /* !CONFIG_OF */
|
||||
static inline struct power_supply *
|
||||
power_supply_get_by_phandle(struct device_node *np, const char *property)
|
||||
{ return NULL; }
|
||||
static inline struct power_supply *
|
||||
devm_power_supply_get_by_phandle(struct device *dev, const char *property)
|
||||
{ return NULL; }
|
||||
#endif /* CONFIG_OF */
|
||||
extern void power_supply_changed(struct power_supply *psy);
|
||||
extern int power_supply_am_i_supplied(struct power_supply *psy);
|
||||
|
Loading…
Reference in New Issue
Block a user