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:
Linus Torvalds 2015-06-23 16:10:27 -07:00
commit 36a1624d88
29 changed files with 5122 additions and 88 deletions

View 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>;
};

View 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>;
};

View 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>;
};

View file

@ -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>;
};

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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

View 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");

View file

@ -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 },
{},
};

View file

@ -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,

View file

@ -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:

View 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");

View 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");

View file

@ -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",

View file

@ -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,
};

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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 */ }

View file

@ -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;
}

View file

@ -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;

View file

@ -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;
}

File diff suppressed because it is too large Load diff

View file

@ -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");

View file

@ -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);

View file

@ -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 */

View file

@ -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_ */

View file

@ -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);