Commit 36a1624d authored by Linus Torvalds's avatar Linus Torvalds

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
  ...
parents 5262f25f 43df6105
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>;
};
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>;
};
Binding for Richtek rt9455 battery charger
Required properties:
- compatible: it should contain one of the following:
"richtek,rt9455".
- reg: integer, i2c address of the device.
- interrupt-parent: the phandle for the interrupt controller that
services interrupts for this device.
- interrupts: interrupt mapping for GPIO IRQ, it should be
configured with IRQ_TYPE_LEVEL_LOW flag.
- richtek,output-charge-current: integer, output current from the charger to the
battery, in uA.
- richtek,end-of-charge-percentage: integer, percent of the output charge current.
When the current in constant-voltage phase drops
below output_charge_current x end-of-charge-percentage,
charge is terminated.
- richtek,battery-regulation-voltage: integer, maximum battery voltage in uV.
- richtek,boost-output-voltage: integer, maximum voltage provided to consumer
devices, when the charger is in boost mode, in uV.
Optional properties:
- richtek,min-input-voltage-regulation: integer, input voltage level in uV, used to
decrease voltage level when the over current
of the input power source occurs.
This prevents input voltage drop due to insufficient
current provided by the power source.
Default: 4500000 uV (4.5V)
- richtek,avg-input-current-regulation: integer, input current value in uA drained by the
charger from the power source.
Default: 500000 uA (500mA)
Example:
rt9455@22 {
compatible = "richtek,rt9455";
reg = <0x22>;
interrupt-parent = <&gpio1>;
interrupts = <0 IRQ_TYPE_LEVEL_LOW>;
richtek,output-charge-current = <500000>;
richtek,end-of-charge-percentage = <10>;
richtek,battery-regulation-voltage = <4200000>;
richtek,boost-output-voltage = <5050000>;
richtek,min-input-voltage-regulation = <4500000>;
richtek,avg-input-current-regulation = <500000>;
};
......@@ -9,10 +9,23 @@ Optional properties :
(datasheet-recommended value is 10000).
Defining this property enables current-sense functionality.
Optional threshold properties :
If skipped the condition won't be reported.
- maxim,cold-temp : Temperature threshold to report battery
as cold (in tenths of degree Celsius).
- maxim,over-heat-temp : Temperature threshold to report battery
as over heated (in tenths of degree Celsius).
- maxim,dead-volt : Voltage threshold to report battery
as dead (in mV).
- maxim,over-volt : Voltage threshold to report battery
as over voltage (in mV).
Example:
battery-charger@36 {
compatible = "maxim,max17042";
reg = <0x36>;
maxim,rsns-microohm = <10000>;
maxim,over-heat-temp = <600>;
maxim,over-volt = <4300>;
};
......@@ -161,6 +161,7 @@ ralink Mediatek/Ralink Technology Corp.
ramtron Ramtron International
realtek Realtek Semiconductor Corp.
renesas Renesas Electronics Corporation
richtek Richtek Technology Corporation
ricoh Ricoh Co. Ltd.
rockchip Fuzhou Rockchip Electronics Co., Ltd
samsung Samsung Semiconductor
......
......@@ -742,7 +742,6 @@ static int pm860x_charger_remove(struct platform_device *pdev)
int i;
power_supply_unregister(info->usb);
free_irq(info->irq[0], info);
for (i = 0; i < info->irq_nums; i++)
free_irq(info->irq[i], info);
return 0;
......
......@@ -204,6 +204,13 @@ config CHARGER_DA9150
This driver can also be built as a module. If so, the module will be
called da9150-charger.
config AXP288_CHARGER
tristate "X-Powers AXP288 Charger"
depends on MFD_AXP20X && EXTCON_AXP288
help
Say yes here to have support X-Power AXP288 power management IC (PMIC)
integrated charger.
config AXP288_FUEL_GAUGE
tristate "X-Powers AXP288 Fuel Gauge"
depends on MFD_AXP20X && IIO
......@@ -388,12 +395,26 @@ config CHARGER_BQ24190
help
Say Y to enable support for the TI BQ24190 battery charger.
config CHARGER_BQ24257
tristate "TI BQ24257 battery charger driver"
depends on I2C && GPIOLIB
depends on REGMAP_I2C
help
Say Y to enable support for the TI BQ24257 battery charger.
config CHARGER_BQ24735
tristate "TI BQ24735 battery charger support"
depends on I2C && GPIOLIB
help
Say Y to enable support for the TI BQ24735 battery charger.
config CHARGER_BQ25890
tristate "TI BQ25890 battery charger driver"
depends on I2C && GPIOLIB
select REGMAP_I2C
help
Say Y to enable support for the TI BQ25890 battery charger.
config CHARGER_SMB347
tristate "Summit Microelectronics SMB347 Battery Charger"
depends on I2C
......@@ -439,6 +460,13 @@ config BATTERY_RT5033
The fuelgauge calculates and determines the battery state of charge
according to battery open circuit voltage.
config CHARGER_RT9455
tristate "Richtek RT9455 battery charger driver"
depends on I2C && GPIOLIB
select REGMAP_I2C
help
Say Y to enable support for Richtek RT9455 battery charger.
source "drivers/power/reset/Kconfig"
endif # POWER_SUPPLY
......
......@@ -37,6 +37,7 @@ obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o
obj-$(CONFIG_BATTERY_MAX17042) += max17042_battery.o
obj-$(CONFIG_BATTERY_Z2) += z2_battery.o
obj-$(CONFIG_BATTERY_RT5033) += rt5033_battery.o
obj-$(CONFIG_CHARGER_RT9455) += rt9455_charger.o
obj-$(CONFIG_BATTERY_S3C_ADC) += s3c_adc_battery.o
obj-$(CONFIG_BATTERY_TWL4030_MADC) += twl4030_madc_battery.o
obj-$(CONFIG_CHARGER_88PM860X) += 88pm860x_charger.o
......@@ -58,9 +59,12 @@ obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o
obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o
obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o
obj-$(CONFIG_CHARGER_BQ24190) += bq24190_charger.o
obj-$(CONFIG_CHARGER_BQ24257) += bq24257_charger.o
obj-$(CONFIG_CHARGER_BQ24735) += bq24735-charger.o
obj-$(CONFIG_CHARGER_BQ25890) += bq25890_charger.o
obj-$(CONFIG_POWER_AVS) += avs/
obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o
obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o
obj-$(CONFIG_POWER_RESET) += reset/
obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o
obj-$(CONFIG_AXP288_CHARGER) += axp288_charger.o
/*
* axp288_charger.c - X-power AXP288 PMIC Charger driver
*
* Copyright (C) 2014 Intel Corporation
* Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/device.h>
#include <linux/regmap.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/usb/otg.h>
#include <linux/notifier.h>
#include <linux/power_supply.h>
#include <linux/notifier.h>
#include <linux/property.h>
#include <linux/mfd/axp20x.h>
#include <linux/extcon.h>
#define PS_STAT_VBUS_TRIGGER (1 << 0)
#define PS_STAT_BAT_CHRG_DIR (1 << 2)
#define PS_STAT_VBAT_ABOVE_VHOLD (1 << 3)
#define PS_STAT_VBUS_VALID (1 << 4)
#define PS_STAT_VBUS_PRESENT (1 << 5)
#define CHRG_STAT_BAT_SAFE_MODE (1 << 3)
#define CHRG_STAT_BAT_VALID (1 << 4)
#define CHRG_STAT_BAT_PRESENT (1 << 5)
#define CHRG_STAT_CHARGING (1 << 6)
#define CHRG_STAT_PMIC_OTP (1 << 7)
#define VBUS_ISPOUT_CUR_LIM_MASK 0x03
#define VBUS_ISPOUT_CUR_LIM_BIT_POS 0
#define VBUS_ISPOUT_CUR_LIM_900MA 0x0 /* 900mA */
#define VBUS_ISPOUT_CUR_LIM_1500MA 0x1 /* 1500mA */
#define VBUS_ISPOUT_CUR_LIM_2000MA 0x2 /* 2000mA */
#define VBUS_ISPOUT_CUR_NO_LIM 0x3 /* 2500mA */
#define VBUS_ISPOUT_VHOLD_SET_MASK 0x31
#define VBUS_ISPOUT_VHOLD_SET_BIT_POS 0x3
#define VBUS_ISPOUT_VHOLD_SET_OFFSET 4000 /* 4000mV */
#define VBUS_ISPOUT_VHOLD_SET_LSB_RES 100 /* 100mV */
#define VBUS_ISPOUT_VHOLD_SET_4300MV 0x3 /* 4300mV */
#define VBUS_ISPOUT_VBUS_PATH_DIS (1 << 7)
#define CHRG_CCCV_CC_MASK 0xf /* 4 bits */
#define CHRG_CCCV_CC_BIT_POS 0
#define CHRG_CCCV_CC_OFFSET 200 /* 200mA */
#define CHRG_CCCV_CC_LSB_RES 200 /* 200mA */
#define CHRG_CCCV_ITERM_20P (1 << 4) /* 20% of CC */
#define CHRG_CCCV_CV_MASK 0x60 /* 2 bits */
#define CHRG_CCCV_CV_BIT_POS 5
#define CHRG_CCCV_CV_4100MV 0x0 /* 4.10V */
#define CHRG_CCCV_CV_4150MV 0x1 /* 4.15V */
#define CHRG_CCCV_CV_4200MV 0x2 /* 4.20V */
#define CHRG_CCCV_CV_4350MV 0x3 /* 4.35V */
#define CHRG_CCCV_CHG_EN (1 << 7)
#define CNTL2_CC_TIMEOUT_MASK 0x3 /* 2 bits */
#define CNTL2_CC_TIMEOUT_OFFSET 6 /* 6 Hrs */
#define CNTL2_CC_TIMEOUT_LSB_RES 2 /* 2 Hrs */
#define CNTL2_CC_TIMEOUT_12HRS 0x3 /* 12 Hrs */
#define CNTL2_CHGLED_TYPEB (1 << 4)
#define CNTL2_CHG_OUT_TURNON (1 << 5)
#define CNTL2_PC_TIMEOUT_MASK 0xC0
#define CNTL2_PC_TIMEOUT_OFFSET 40 /* 40 mins */
#define CNTL2_PC_TIMEOUT_LSB_RES 10 /* 10 mins */
#define CNTL2_PC_TIMEOUT_70MINS 0x3
#define CHRG_ILIM_TEMP_LOOP_EN (1 << 3)
#define CHRG_VBUS_ILIM_MASK 0xf0
#define CHRG_VBUS_ILIM_BIT_POS 4
#define CHRG_VBUS_ILIM_100MA 0x0 /* 100mA */
#define CHRG_VBUS_ILIM_500MA 0x1 /* 500mA */
#define CHRG_VBUS_ILIM_900MA 0x2 /* 900mA */
#define CHRG_VBUS_ILIM_1500MA 0x3 /* 1500mA */
#define CHRG_VBUS_ILIM_2000MA 0x4 /* 2000mA */
#define CHRG_VBUS_ILIM_2500MA 0x5 /* 2500mA */
#define CHRG_VBUS_ILIM_3000MA 0x6 /* 3000mA */
#define CHRG_VLTFC_0C 0xA5 /* 0 DegC */
#define CHRG_VHTFC_45C 0x1F /* 45 DegC */
#define BAT_IRQ_CFG_CHRG_DONE (1 << 2)
#define BAT_IRQ_CFG_CHRG_START (1 << 3)
#define BAT_IRQ_CFG_BAT_SAFE_EXIT (1 << 4)
#define BAT_IRQ_CFG_BAT_SAFE_ENTER (1 << 5)
#define BAT_IRQ_CFG_BAT_DISCON (1 << 6)
#define BAT_IRQ_CFG_BAT_CONN (1 << 7)
#define BAT_IRQ_CFG_BAT_MASK 0xFC
#define TEMP_IRQ_CFG_QCBTU (1 << 4)
#define TEMP_IRQ_CFG_CBTU (1 << 5)
#define TEMP_IRQ_CFG_QCBTO (1 << 6)
#define TEMP_IRQ_CFG_CBTO (1 << 7)
#define TEMP_IRQ_CFG_MASK 0xF0
#define FG_CNTL_OCV_ADJ_EN (1 << 3)
#define CV_4100MV 4100 /* 4100mV */
#define CV_4150MV 4150 /* 4150mV */
#define CV_4200MV 4200 /* 4200mV */
#define CV_4350MV 4350 /* 4350mV */
#define CC_200MA 200 /* 200mA */
#define CC_600MA 600 /* 600mA */
#define CC_800MA 800 /* 800mA */
#define CC_1000MA 1000 /* 1000mA */
#define CC_1600MA 1600 /* 1600mA */
#define CC_2000MA 2000 /* 2000mA */
#define ILIM_100MA 100 /* 100mA */
#define ILIM_500MA 500 /* 500mA */
#define ILIM_900MA 900 /* 900mA */
#define ILIM_1500MA 1500 /* 1500mA */
#define ILIM_2000MA 2000 /* 2000mA */
#define ILIM_2500MA 2500 /* 2500mA */
#define ILIM_3000MA 3000 /* 3000mA */
#define AXP288_EXTCON_DEV_NAME "axp288_extcon"
#define AXP288_EXTCON_SLOW_CHARGER "SLOW-CHARGER"
#define AXP288_EXTCON_DOWNSTREAM_CHARGER "CHARGE-DOWNSTREAM"
#define AXP288_EXTCON_FAST_CHARGER "FAST-CHARGER"
enum {
VBUS_OV_IRQ = 0,
CHARGE_DONE_IRQ,
CHARGE_CHARGING_IRQ,
BAT_SAFE_QUIT_IRQ,
BAT_SAFE_ENTER_IRQ,
QCBTU_IRQ,
CBTU_IRQ,
QCBTO_IRQ,
CBTO_IRQ,
CHRG_INTR_END,
};
struct axp288_chrg_info {
struct platform_device *pdev;
struct axp20x_chrg_pdata *pdata;
struct regmap *regmap;
struct regmap_irq_chip_data *regmap_irqc;
int irq[CHRG_INTR_END];
struct power_supply *psy_usb;
struct mutex lock;
/* OTG/Host mode */
struct {
struct work_struct work;
struct extcon_specific_cable_nb cable;
struct notifier_block id_nb;
bool id_short;
} otg;
/* SDP/CDP/DCP USB charging cable notifications */
struct {
struct extcon_dev *edev;
bool connected;
enum power_supply_type chg_type;
struct notifier_block nb;
struct work_struct work;
} cable;
int health;
int inlmt;
int cc;
int cv;
int max_cc;
int max_cv;
bool online;
bool present;
bool enable_charger;
bool is_charger_enabled;
};
static inline int axp288_charger_set_cc(struct axp288_chrg_info *info, int cc)
{
u8 reg_val;
int ret;
if (cc < CHRG_CCCV_CC_OFFSET)
cc = CHRG_CCCV_CC_OFFSET;
else if (cc > info->max_cc)
cc = info->max_cc;
reg_val = (cc - CHRG_CCCV_CC_OFFSET) / CHRG_CCCV_CC_LSB_RES;
cc = (reg_val * CHRG_CCCV_CC_LSB_RES) + CHRG_CCCV_CC_OFFSET;
reg_val = reg_val << CHRG_CCCV_CC_BIT_POS;
ret = regmap_update_bits(info->regmap,
AXP20X_CHRG_CTRL1,
CHRG_CCCV_CC_MASK, reg_val);
if (ret >= 0)
info->cc = cc;
return ret;
}
static inline int axp288_charger_set_cv(struct axp288_chrg_info *info, int cv)
{
u8 reg_val;
int ret;
if (cv <= CV_4100MV) {
reg_val = CHRG_CCCV_CV_4100MV;
cv = CV_4100MV;
} else if (cv <= CV_4150MV) {
reg_val = CHRG_CCCV_CV_4150MV;
cv = CV_4150MV;
} else if (cv <= CV_4200MV) {
reg_val = CHRG_CCCV_CV_4200MV;
cv = CV_4200MV;
} else {
reg_val = CHRG_CCCV_CV_4350MV;
cv = CV_4350MV;
}
reg_val = reg_val << CHRG_CCCV_CV_BIT_POS;
ret = regmap_update_bits(info->regmap,
AXP20X_CHRG_CTRL1,
CHRG_CCCV_CV_MASK, reg_val);
if (ret >= 0)
info->cv = cv;
return ret;
}
static inline int axp288_charger_set_vbus_inlmt(struct axp288_chrg_info *info,
int inlmt)
{
int ret;
unsigned int val;
u8 reg_val;
/* Read in limit register */
ret = regmap_read(info->regmap, AXP20X_CHRG_BAK_CTRL, &val);
if (ret < 0)
goto set_inlmt_fail;
if (inlmt <= ILIM_100MA) {
reg_val = CHRG_VBUS_ILIM_100MA;
inlmt = ILIM_100MA;
} else if (inlmt <= ILIM_500MA) {
reg_val = CHRG_VBUS_ILIM_500MA;
inlmt = ILIM_500MA;
} else if (inlmt <= ILIM_900MA) {
reg_val = CHRG_VBUS_ILIM_900MA;
inlmt = ILIM_900MA;
} else if (inlmt <= ILIM_1500MA) {
reg_val = CHRG_VBUS_ILIM_1500MA;
inlmt = ILIM_1500MA;
} else if (inlmt <= ILIM_2000MA) {
reg_val = CHRG_VBUS_ILIM_2000MA;
inlmt = ILIM_2000MA;
} else if (inlmt <= ILIM_2500MA) {
reg_val = CHRG_VBUS_ILIM_2500MA;
inlmt = ILIM_2500MA;
} else {
reg_val = CHRG_VBUS_ILIM_3000MA;
inlmt = ILIM_3000MA;
}
reg_val = (val & ~CHRG_VBUS_ILIM_MASK)
| (reg_val << CHRG_VBUS_ILIM_BIT_POS);
ret = regmap_write(info->regmap, AXP20X_CHRG_BAK_CTRL, reg_val);
if (ret >= 0)
info->inlmt = inlmt;
else
dev_err(&info->pdev->dev, "charger BAK control %d\n", ret);
set_inlmt_fail:
return ret;
}
static int axp288_charger_vbus_path_select(struct axp288_chrg_info *info,
bool enable)
{
int ret;
if (enable)
ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT,
VBUS_ISPOUT_VBUS_PATH_DIS, 0);
else
ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT,
VBUS_ISPOUT_VBUS_PATH_DIS, VBUS_ISPOUT_VBUS_PATH_DIS);
if (ret < 0)
dev_err(&info->pdev->dev, "axp288 vbus path select %d\n", ret);
return ret;
}
static int axp288_charger_enable_charger(struct axp288_chrg_info *info,
bool enable)
{
int ret;
if (enable)
ret = regmap_update_bits(info->regmap, AXP20X_CHRG_CTRL1,
CHRG_CCCV_CHG_EN, CHRG_CCCV_CHG_EN);
else
ret = regmap_update_bits(info->regmap, AXP20X_CHRG_CTRL1,
CHRG_CCCV_CHG_EN, 0);
if (ret < 0)
dev_err(&info->pdev->dev, "axp288 enable charger %d\n", ret);
else
info->is_charger_enabled = enable;
return ret;
}
static int axp288_charger_is_present(struct axp288_chrg_info *info)
{
int ret, present = 0;
unsigned int val;
ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val);
if (ret < 0)
return ret;
if (val & PS_STAT_VBUS_PRESENT)
present = 1;
return present;
}
static int axp288_charger_is_online(struct axp288_chrg_info *info)
{
int ret, online = 0;
unsigned int val;
ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val);
if (ret < 0)
return ret;
if (val & PS_STAT_VBUS_VALID)
online = 1;
return online;
}
static int axp288_get_charger_health(struct axp288_chrg_info *info)
{
int ret, pwr_stat, chrg_stat;
int health = POWER_SUPPLY_HEALTH_UNKNOWN;
unsigned int val;
ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val);
if ((ret < 0) || !(val & PS_STAT_VBUS_PRESENT))
goto health_read_fail;
else
pwr_stat = val;
ret = regmap_read(info->regmap, AXP20X_PWR_OP_MODE, &val);
if (ret < 0)
goto health_read_fail;
else
chrg_stat = val;
if (!(pwr_stat & PS_STAT_VBUS_VALID))
health = POWER_SUPPLY_HEALTH_DEAD;
else if (chrg_stat & CHRG_STAT_PMIC_OTP)
health = POWER_SUPPLY_HEALTH_OVERHEAT;
else if (chrg_stat & CHRG_STAT_BAT_SAFE_MODE)
health = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
else
health = POWER_SUPPLY_HEALTH_GOOD;
health_read_fail:
return health;
}
static int axp288_charger_usb_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct axp288_chrg_info *info = power_supply_get_drvdata(psy);
int ret = 0;
int scaled_val;
mutex_lock(&info->lock);
switch (psp) {
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
scaled_val = min(val->intval, info->max_cc);
scaled_val = DIV_ROUND_CLOSEST(scaled_val, 1000);
ret = axp288_charger_set_cc(info, scaled_val);
if (ret < 0)
dev_warn(&info->pdev->dev, "set charge current failed\n");
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
scaled_val = min(val->intval, info->max_cv);
scaled_val = DIV_ROUND_CLOSEST(scaled_val, 1000);
ret = axp288_charger_set_cv(info, scaled_val);
if (ret < 0)
dev_warn(&info->pdev->dev, "set charge voltage failed\n");
break;
default:
ret = -EINVAL;
}
mutex_unlock(&info->lock);
return ret;
}
static int axp288_charger_usb_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct axp288_chrg_info *info = power_supply_get_drvdata(psy);
int ret = 0;
mutex_lock(&info->lock);
switch (psp) {
case POWER_SUPPLY_PROP_PRESENT:
/* Check for OTG case first */
if (info->otg.id_short) {
val->intval = 0;
break;
}
ret = axp288_charger_is_present(info);
if (ret < 0)
goto psy_get_prop_fail;
info->present = ret;
val->intval = info->present;
break;
case POWER_SUPPLY_PROP_ONLINE:
/* Check for OTG case first */
if (info->otg.id_short) {
val->intval = 0;
break;
}
ret = axp288_charger_is_online(info);
if (ret < 0)
goto psy_get_prop_fail;
info->online = ret;
val->intval = info->online;
break;
case POWER_SUPPLY_PROP_HEALTH:
val->intval = axp288_get_charger_health(info);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
val->intval = info->cc * 1000;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
val->intval = info->max_cc * 1000;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
val->intval = info->cv * 1000;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
val->intval = info->max_cv * 1000;
break;
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
val->intval = info->inlmt * 1000;
break;
default:
ret = -EINVAL;
goto psy_get_prop_fail;
}
psy_get_prop_fail:
mutex_unlock(&info->lock);
return ret;
}
static int axp288_charger_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
int ret;
switch (psp) {
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
ret = 1;
break;
default:
ret = 0;
}
return ret;
}
static enum power_supply_property axp288_usb_props[] = {
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_TYPE,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
};
static const struct power_supply_desc axp288_charger_desc = {
.name = "axp288_charger",
.type = POWER_SUPPLY_TYPE_USB,
.properties = axp288_usb_props,
.num_properties = ARRAY_SIZE(axp288_usb_props),
.get_property = axp288_charger_usb_get_property,
.set_property = axp288_charger_usb_set_property,
.property_is_writeable = axp288_charger_property_is_writeable,
};
static irqreturn_t axp288_charger_irq_thread_handler(int irq, void *dev)
{
struct axp288_chrg_info *info = dev;
int i;
for (i = 0; i < CHRG_INTR_END; i++) {
if (info->irq[i] == irq)
break;
}
if (i >= CHRG_INTR_END) {
dev_warn(&info->pdev->dev, "spurious interrupt!!\n");
return IRQ_NONE;
}
switch (i) {
case VBUS_OV_IRQ:
dev_dbg(&info->pdev->dev, "VBUS Over Voltage INTR\n");
break;
case CHARGE_DONE_IRQ:
dev_dbg(&info->pdev->dev, "Charging Done INTR\n");
break;
case CHARGE_CHARGING_IRQ:
dev_dbg(&info->pdev->dev, "Start Charging IRQ\n");
break;
case BAT_SAFE_QUIT_IRQ:
dev_dbg(&info->pdev->dev,
"Quit Safe Mode(restart timer) Charging IRQ\n");
break;
case BAT_SAFE_ENTER_IRQ:
dev_dbg(&info->pdev->dev,
"Enter Safe Mode(timer expire) Charging IRQ\n");
break;
case QCBTU_IRQ:
dev_dbg(&info->pdev->dev,
"Quit Battery Under Temperature(CHRG) INTR\n");
break;
case CBTU_IRQ:
dev_dbg(&info->pdev->dev,
"Hit Battery Under Temperature(CHRG) INTR\n");
break;
case QCBTO_IRQ:
dev_dbg(&info->pdev->dev,
"Quit Battery Over Temperature(CHRG) INTR\n");
break;
case CBTO_IRQ:
dev_dbg(&info->pdev->dev,
"Hit Battery Over Temperature(CHRG) INTR\n");
break;
default:
dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n");
goto out;
}
power_supply_changed(info->psy_usb);
out:
return IRQ_HANDLED;
}
static void axp288_charger_extcon_evt_worker(struct work_struct *work)
{
struct axp288_chrg_info *info =
container_of(work, struct axp288_chrg_info, cable.work);
int ret, current_limit;
bool changed = false;
struct extcon_dev *edev = info->cable.edev;
bool old_connected = info->cable.connected;
/* Determine cable/charger type */
if (extcon_get_cable_state(edev, AXP288_EXTCON_SLOW_CHARGER) > 0) {
dev_dbg(&info->pdev->dev, "USB SDP charger is connected");
info->cable.connected = true;
info->cable.chg_type = POWER_SUPPLY_TYPE_USB;
} else if (extcon_get_cable_state(edev,
AXP288_EXTCON_DOWNSTREAM_CHARGER) > 0) {
dev_dbg(&info->pdev->dev, "USB CDP charger is connected");
info->cable.connected = true;
info->cable.chg_type = POWER_SUPPLY_TYPE_USB_CDP;
} else if (extcon_get_cable_state(edev,
AXP288_EXTCON_FAST_CHARGER) > 0) {
dev_dbg(&info->pdev->dev, "USB DCP charger is connected");
info->cable.connected = true;
info->cable.chg_type = POWER_SUPPLY_TYPE_USB_DCP;
} else {
if (old_connected)
dev_dbg(&info->pdev->dev, "USB charger disconnected");
info->cable.connected = false;
info->cable.chg_type = POWER_SUPPLY_TYPE_USB;
}
/* Cable status changed */
if (old_connected != info->cable.connected)
changed = true;
if (!changed)
return;
mutex_lock(&info->lock);
if (info->is_charger_enabled && !info->cable.connected) {
info->enable_charger = false;
ret = axp288_charger_enable_charger(info, info->enable_charger);
if (ret < 0)
dev_err(&info->pdev->dev,
"cannot disable charger (%d)", ret);
} else if (!info->is_charger_enabled && info->cable.connected) {
switch (info->cable.chg_type) {
case POWER_SUPPLY_TYPE_USB:
current_limit = ILIM_500MA;
break;
case POWER_SUPPLY_TYPE_USB_CDP:
current_limit = ILIM_1500MA;
break;
case POWER_SUPPLY_TYPE_USB_DCP:
current_limit = ILIM_2000MA;
break;
default:
/* Unknown */
current_limit = 0;
break;
}
/* Set vbus current limit first, then enable charger */
ret = axp288_charger_set_vbus_inlmt(info, current_limit);
if (ret < 0) {
dev_err(&info->pdev->dev,
"error setting current limit (%d)", ret);
} else {
info->enable_charger = (current_limit > 0);
ret = axp288_charger_enable_charger(info,
info->enable_charger);
if (ret < 0)
dev_err(&info->pdev->dev,
"cannot enable charger (%d)", ret);
}
}
if (changed)
info->health = axp288_get_charger_health(info);
mutex_unlock(&info->lock);
if (changed)
power_supply_changed(info->psy_usb);
}
static int axp288_charger_handle_cable_evt(struct notifier_block *nb,
unsigned long event, void *param)
{
struct axp288_chrg_info *info =
container_of(nb, struct axp288_chrg_info, cable.nb);
schedule_work(&info->cable.work);
return NOTIFY_OK;
}
static void axp288_charger_otg_evt_worker(struct work_struct *work)
{
struct axp288_chrg_info *info =
container_of(work, struct axp288_chrg_info, otg.work);
int ret;
/* Disable VBUS path before enabling the 5V boost */
ret = axp288_charger_vbus_path_select(info, !info->otg.id_short);
if (ret < 0)
dev_warn(&info->pdev->dev, "vbus path disable failed\n");
}
static int axp288_charger_handle_otg_evt(struct notifier_block *nb,
unsigned long event, void *param)
{
struct axp288_chrg_info *info =
container_of(nb, struct axp288_chrg_info, otg.id_nb);
struct extcon_dev *edev = param;
int usb_host = extcon_get_cable_state(edev, "USB-Host");
dev_dbg(&info->pdev->dev, "external connector USB-Host is %s\n",
usb_host ? "attached" : "detached");
/*
* Set usb_id_short flag to avoid running charger detection logic
* in case usb host.
*/
info->otg.id_short = usb_host;
schedule_work(&info->otg.work);
return NOTIFY_OK;
}
static void charger_init_hw_regs(struct axp288_chrg_info *info)
{
int ret, cc, cv;
unsigned int val;
/* Program temperature thresholds */
ret = regmap_write(info->regmap, AXP20X_V_LTF_CHRG, CHRG_VLTFC_0C);
if (ret < 0)
dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
AXP20X_V_LTF_CHRG, ret);
ret = regmap_write(info->regmap, AXP20X_V_HTF_CHRG, CHRG_VHTFC_45C);
if (ret < 0)
dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
AXP20X_V_HTF_CHRG, ret);
/* Do not turn-off charger o/p after charge cycle ends */
ret = regmap_update_bits(info->regmap,
AXP20X_CHRG_CTRL2,
CNTL2_CHG_OUT_TURNON, 1);
if (ret < 0)
dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
AXP20X_CHRG_CTRL2, ret);
/* Enable interrupts */
ret = regmap_update_bits(info->regmap,
AXP20X_IRQ2_EN,
BAT_IRQ_CFG_BAT_MASK, 1);
if (ret < 0)
dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
AXP20X_IRQ2_EN, ret);
ret = regmap_update_bits(info->regmap, AXP20X_IRQ3_EN,
TEMP_IRQ_CFG_MASK, 1);
if (ret < 0)
dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
AXP20X_IRQ3_EN, ret);
/* Setup ending condition for charging to be 10% of I(chrg) */
ret = regmap_update_bits(info->regmap,
AXP20X_CHRG_CTRL1,
CHRG_CCCV_ITERM_20P, 0);
if (ret < 0)
dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
AXP20X_CHRG_CTRL1, ret);
/* Disable OCV-SOC curve calibration */
ret = regmap_update_bits(info->regmap,
AXP20X_CC_CTRL,
FG_CNTL_OCV_ADJ_EN, 0);
if (ret < 0)
dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
AXP20X_CC_CTRL, ret);
/* Init charging current and voltage */
info->max_cc = info->pdata->max_cc;
info->max_cv = info->pdata->max_cv;
/* Read current charge voltage and current limit */
ret = regmap_read(info->regmap, AXP20X_CHRG_CTRL1, &val);
if (ret < 0) {
/* Assume default if cannot read */
info->cc = info->pdata->def_cc;
info->cv = info->pdata->def_cv;
} else {
/* Determine charge voltage */
cv = (val & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS;
switch (cv) {
case CHRG_CCCV_CV_4100MV:
info->cv = CV_4100MV;
break;
case CHRG_CCCV_CV_4150MV:
info->cv = CV_4150MV;
break;
case CHRG_CCCV_CV_4200MV:
info->cv = CV_4200MV;
break;
case CHRG_CCCV_CV_4350MV:
info->cv = CV_4350MV;
break;
default:
info->cv = INT_MAX;
break;
}
/* Determine charge current limit */
cc = (ret & CHRG_CCCV_CC_MASK) >> CHRG_CCCV_CC_BIT_POS;
cc = (cc * CHRG_CCCV_CC_LSB_RES) + CHRG_CCCV_CC_OFFSET;
info->cc = cc;
/* Program default charging voltage and current */
cc = min(info->pdata->def_cc, info->max_cc);
cv = min(info->pdata->def_cv, info->max_cv);
ret = axp288_charger_set_cc(info, cc);
if (ret < 0)
dev_warn(&info->pdev->dev,
"error(%d) in setting CC\n", ret);
ret = axp288_charger_set_cv(info, cv);
if (ret < 0)
dev_warn(&info->pdev->dev,
"error(%d) in setting CV\n", ret);
}
}
static int axp288_charger_probe(struct platform_device *pdev)
{
int ret, i, pirq;
struct axp288_chrg_info *info;
struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
struct power_supply_config charger_cfg = {};
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
info->pdev = pdev;
info->regmap = axp20x->regmap;
info->regmap_irqc = axp20x->regmap_irqc;
info->pdata = pdev->dev.platform_data;
if (!info->pdata) {
/* Try ACPI provided pdata via device properties */
if (!device_property_present(&pdev->dev,
"axp288_charger_data\n"))
dev_err(&pdev->dev, "failed to get platform data\n");
return -ENODEV;
}
info->cable.edev = extcon_get_extcon_dev(AXP288_EXTCON_DEV_NAME);
if (info->cable.edev == NULL) {
dev_dbg(&pdev->dev, "%s is not ready, probe deferred\n",
AXP288_EXTCON_DEV_NAME);
return -EPROBE_DEFER;
}
/* Register for extcon notification */
INIT_WORK(&info->cable.work, axp288_charger_extcon_evt_worker);
info->cable.nb.notifier_call = axp288_charger_handle_cable_evt;
ret = extcon_register_notifier(info->cable.edev, &info->cable.nb);
if (ret) {
dev_err(&info->pdev->dev,
"failed to register extcon notifier %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, info);
mutex_init(&info->lock);
/* Register with power supply class */
charger_cfg.drv_data = info;
info->psy_usb = power_supply_register(&pdev->dev, &axp288_charger_desc,
&charger_cfg);
if (IS_ERR(info->psy_usb)) {
dev_err(&pdev->dev, "failed to register power supply charger\n");
ret = PTR_ERR(info->psy_usb);
goto psy_reg_failed;
}
/* Register for OTG notification */
INIT_WORK(&info->otg.work, axp288_charger_otg_evt_worker);
info->otg.id_nb.notifier_call = axp288_charger_handle_otg_evt;
ret = extcon_register_interest(&info->otg.cable, NULL, "USB-Host",
&info->otg.id_nb);
if (ret)
dev_warn(&pdev->dev, "failed to register otg notifier\n");
if (info->otg.cable.edev)
info->otg.id_short = extcon_get_cable_state(
info->otg.cable.edev, "USB-Host");
/* Register charger interrupts */
for (i = 0; i < CHRG_INTR_END; i++) {
pirq = platform_get_irq(info->pdev, i);
info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
if (info->irq[i] < 0) {
dev_warn(&info->pdev->dev,
"failed to get virtual interrupt=%d\n", pirq);
ret = info->irq[i];
goto intr_reg_failed;
}
ret = devm_request_threaded_irq(&info->pdev->dev, info->irq[i],
NULL, axp288_charger_irq_thread_handler,
IRQF_ONESHOT, info->pdev->name, info);
if (ret) {
dev_err(&pdev->dev, "failed to request interrupt=%d\n",
info->irq[i]);
goto intr_reg_failed;
}
}
charger_init_hw_regs(info);
return 0;
intr_reg_failed:
if (info->otg.cable.edev)
extcon_unregister_interest(&info->otg.cable);
power_supply_unregister(info->psy_usb);
psy_reg_failed:
extcon_unregister_notifier(info->cable.edev, &info->cable.nb);
return ret;
}
static int axp288_charger_remove(struct platform_device *pdev)
{
struct axp288_chrg_info *info = dev_get_drvdata(&pdev->dev);
if (info->otg.cable.edev)
extcon_unregister_interest(&info->otg.cable);
extcon_unregister_notifier(info->cable.edev, &info->cable.nb);
power_supply_unregister(info->psy_usb);
return 0;
}
static struct platform_driver axp288_charger_driver = {
.probe = axp288_charger_probe,
.remove = axp288_charger_remove,
.driver = {
.name = "axp288_charger",
},
};
module_platform_driver(axp288_charger_driver);
MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
MODULE_DESCRIPTION("X-power AXP288 Charger Driver");
MODULE_LICENSE("GPL v2");
......@@ -1117,7 +1117,7 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev)
return ret;
}
static struct platform_device_id axp288_fg_id_table[] = {
static const struct platform_device_id axp288_fg_id_table[] = {
{ .name = DEV_NAME },
{},
};
......
......@@ -35,6 +35,7 @@
#include <linux/idr.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/acpi.h>
#include <linux/power/bq2415x_charger.h>
......@@ -631,7 +632,7 @@ static int bq2415x_set_charge_current(struct bq2415x_device *bq, int mA)
int val;
if (bq->init_data.resistor_sense <= 0)
return -ENOSYS;
return -EINVAL;
val = (mA * bq->init_data.resistor_sense - 37400) / 6800;
if (val < 0)
......@@ -650,7 +651,7 @@ static int bq2415x_get_charge_current(struct bq2415x_device *bq)
int ret;
if (bq->init_data.resistor_sense <= 0)
return -ENOSYS;
return -EINVAL;
ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CURRENT,
BQ2415X_MASK_VI_CHRG, BQ2415X_SHIFT_VI_CHRG);
......@@ -665,7 +666,7 @@ static int bq2415x_set_termination_current(struct bq2415x_device *bq, int mA)
int val;
if (bq->init_data.resistor_sense <= 0)
return -ENOSYS;
return -EINVAL;
val = (mA * bq->init_data.resistor_sense - 3400) / 3400;
if (val < 0)
......@@ -684,7 +685,7 @@ static int bq2415x_get_termination_current(struct bq2415x_device *bq)
int ret;
if (bq->init_data.resistor_sense <= 0)
return -ENOSYS;
return -EINVAL;
ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CURRENT,
BQ2415X_MASK_VI_TERM, BQ2415X_SHIFT_VI_TERM);
......@@ -1166,7 +1167,7 @@ static ssize_t bq2415x_sysfs_set_mode(struct device *dev,
if (strncmp(buf, "auto", 4) == 0) {
if (bq->automode < 0)
return -ENOSYS;
return -EINVAL;
bq->automode = 1;
mode = bq->reported_mode;
} else if (strncmp(buf, "off", 3) == 0) {
......@@ -1530,13 +1531,14 @@ static int bq2415x_probe(struct i2c_client *client,
{
int ret;
int num;
char *name;
char *name = NULL;
struct bq2415x_device *bq;
struct device_node *np = client->dev.of_node;
struct bq2415x_platform_data *pdata = client->dev.platform_data;
const struct acpi_device_id *acpi_id = NULL;
if (!np && !pdata) {
dev_err(&client->dev, "platform data missing\n");
if (!np && !pdata && !ACPI_HANDLE(&client->dev)) {
dev_err(&client->dev, "Neither devicetree, nor platform data, nor ACPI support\n");
return -ENODEV;
}
......@@ -1547,7 +1549,14 @@ static int bq2415x_probe(struct i2c_client *client,
if (num < 0)
return num;
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,13 +1565,13 @@ 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,
......@@ -1573,45 +1582,54 @@ static int bq2415x_probe(struct i2c_client *client,
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;
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",
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",
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",
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",
ret = device_property_read_u32(bq->dev,
"ti,resistor-sense",
&bq->init_data.resistor_sense);
if (ret)
goto error_3;
......@@ -1648,7 +1666,8 @@ static int bq2415x_probe(struct i2c_client *client,
}
/* Query for initial reported_mode and set it */
bq2415x_notifier_call(&bq->nb, PSY_EVENT_PROP_CHANGED, bq->notify_psy);
bq2415x_notifier_call(&bq->nb, PSY_EVENT_PROP_CHANGED,
bq->notify_psy);
bq2415x_set_mode(bq, bq->reported_mode);
bq->automode = 1;
......@@ -1727,9 +1746,28 @@ static const struct i2c_device_id bq2415x_i2c_id_table[] = {
};
MODULE_DEVICE_TABLE(i2c, bq2415x_i2c_id_table);
static const struct acpi_device_id bq2415x_i2c_acpi_match[] = {
{ "BQ2415X", BQUNKNOWN },
{ "BQ241500", BQ24150 },
{ "BQA24150", BQ24150A },
{ "BQ241510", BQ24151 },
{ "BQA24151", BQ24151A },
{ "BQ241520", BQ24152 },
{ "BQ241530", BQ24153 },
{ "BQA24153", BQ24153A },
{ "BQ241550", BQ24155 },
{ "BQ241560", BQ24156 },
{ "BQA24156", BQ24156A },
{ "BQS24157", BQ24157S },
{ "BQ241580", BQ24158 },
{},
};
MODULE_DEVICE_TABLE(acpi, bq2415x_i2c_acpi_match);
static struct i2c_driver bq2415x_driver = {
.driver = {
.name = "bq2415x-charger",
.acpi_match_table = ACPI_PTR(bq2415x_i2c_acpi_match),
},
.probe = bq2415x_probe,
.remove = bq2415x_remove,
......
......@@ -1258,11 +1258,14 @@ 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) {
if (alert_userspace) {
if (!bdi->first_time) {
power_supply_changed(bdi->charger);
power_supply_changed(bdi->battery);
} else {
bdi->first_time = false;
}
}
out:
pm_runtime_put_sync(bdi->dev);
......
/*
* 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");
/*
* TI BQ25890 charger driver
*
* Copyright (C) 2015 Intel Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/types.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/usb/phy.h>
#include <linux/acpi.h>
#include <linux/of.h>
#define BQ25890_MANUFACTURER "Texas Instruments"
#define BQ25890_IRQ_PIN "bq25890_irq"
#define BQ25890_ID 3
enum bq25890_fields {
F_EN_HIZ, F_EN_ILIM, F_IILIM, /* Reg00 */
F_BHOT, F_BCOLD, F_VINDPM_OFS, /* Reg01 */
F_CONV_START, F_CONV_RATE, F_BOOSTF, F_ICO_EN,
F_HVDCP_EN, F_MAXC_EN, F_FORCE_DPM, F_AUTO_DPDM_EN, /* Reg02 */
F_BAT_LOAD_EN, F_WD_RST, F_OTG_CFG, F_CHG_CFG, F_SYSVMIN, /* Reg03 */
F_PUMPX_EN, F_ICHG, /* Reg04 */
F_IPRECHG, F_ITERM, /* Reg05 */
F_VREG, F_BATLOWV, F_VRECHG, /* Reg06 */
F_TERM_EN, F_STAT_DIS, F_WD, F_TMR_EN, F_CHG_TMR,
F_JEITA_ISET, /* Reg07 */
F_BATCMP, F_VCLAMP, F_TREG, /* Reg08 */
F_FORCE_ICO, F_TMR2X_EN, F_BATFET_DIS, F_JEITA_VSET,
F_BATFET_DLY, F_BATFET_RST_EN, F_PUMPX_UP, F_PUMPX_DN, /* Reg09 */
F_BOOSTV, F_BOOSTI, /* Reg0A */
F_VBUS_STAT, F_CHG_STAT, F_PG_STAT, F_SDP_STAT, F_VSYS_STAT, /* Reg0B */
F_WD_FAULT, F_BOOST_FAULT, F_CHG_FAULT, F_BAT_FAULT,
F_NTC_FAULT, /* Reg0C */
F_FORCE_VINDPM, F_VINDPM, /* Reg0D */
F_THERM_STAT, F_BATV, /* Reg0E */
F_SYSV, /* Reg0F */
F_TSPCT, /* Reg10 */
F_VBUS_GD, F_VBUSV, /* Reg11 */
F_ICHGR, /* Reg12 */
F_VDPM_STAT, F_IDPM_STAT, F_IDPM_LIM, /* Reg13 */
F_REG_RST, F_ICO_OPTIMIZED, F_PN, F_TS_PROFILE, F_DEV_REV, /* Reg14 */
F_MAX_FIELDS
};
/* initial field values, converted to register values */
struct bq25890_init_data {
u8 ichg; /* charge current */
u8 vreg; /* regulation voltage */
u8 iterm; /* termination current */
u8 iprechg; /* precharge current */
u8 sysvmin; /* minimum system voltage limit */
u8 boostv; /* boost regulation voltage */
u8 boosti; /* boost current limit */
u8 boostf; /* boost frequency */
u8 ilim_en; /* enable ILIM pin */
u8 treg; /* thermal regulation threshold */
};
struct bq25890_state {
u8 online;
u8 chrg_status;
u8 chrg_fault;
u8 vsys_status;
u8 boost_fault;
u8 bat_fault;
};
struct bq25890_device {
struct i2c_client *client;
struct device *dev;
struct power_supply *charger;
struct usb_phy *usb_phy;
struct notifier_block usb_nb;
struct work_struct usb_work;
unsigned long usb_event;
struct regmap *rmap;
struct regmap_field *rmap_fields[F_MAX_FIELDS];
int chip_id;
struct bq25890_init_data init_data;
struct bq25890_state state;
struct mutex lock; /* protect state data */
};
static const struct regmap_range bq25890_readonly_reg_ranges[] = {
regmap_reg_range(0x0b, 0x0c),
regmap_reg_range(0x0e, 0x13),
};
static const struct regmap_access_table bq25890_writeable_regs = {
.no_ranges = bq25890_readonly_reg_ranges,
.n_no_ranges = ARRAY_SIZE(bq25890_readonly_reg_ranges),
};
static const struct regmap_range bq25890_volatile_reg_ranges[] = {
regmap_reg_range(0x00, 0x00),
regmap_reg_range(0x09, 0x09),
regmap_reg_range(0x0b, 0x0c),
regmap_reg_range(0x0e, 0x14),
};
static const struct regmap_access_table bq25890_volatile_regs = {
.yes_ranges = bq25890_volatile_reg_ranges,
.n_yes_ranges = ARRAY_SIZE(bq25890_volatile_reg_ranges),
};
static const struct regmap_config bq25890_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = 0x14,
.cache_type = REGCACHE_RBTREE,
.wr_table = &bq25890_writeable_regs,
.volatile_table = &bq25890_volatile_regs,
};
static const struct reg_field bq25890_reg_fields[] = {
/* REG00 */
[F_EN_HIZ] = REG_FIELD(0x00, 7, 7),
[F_EN_ILIM] = REG_FIELD(0x00, 6, 6),
[F_IILIM] = REG_FIELD(0x00, 0, 5),
/* REG01 */
[F_BHOT] = REG_FIELD(0x01, 6, 7),
[F_BCOLD] = REG_FIELD(0x01, 5, 5),
[F_VINDPM_OFS] = REG_FIELD(0x01, 0, 4),
/* REG02 */
[F_CONV_START] = REG_FIELD(0x02, 7, 7),
[F_CONV_RATE] = REG_FIELD(0x02, 6, 6),
[F_BOOSTF] = REG_FIELD(0x02, 5, 5),
[F_ICO_EN] = REG_FIELD(0x02, 4, 4),
[F_HVDCP_EN] = REG_FIELD(0x02, 3, 3),
[F_MAXC_EN] = REG_FIELD(0x02, 2, 2),
[F_FORCE_DPM] = REG_FIELD(0x02, 1, 1),
[F_AUTO_DPDM_EN] = REG_FIELD(0x02, 0, 0),
/* REG03 */
[F_BAT_LOAD_EN] = REG_FIELD(0x03, 7, 7),
[F_WD_RST] = REG_FIELD(0x03, 6, 6),
[F_OTG_CFG] = REG_FIELD(0x03, 5, 5),
[F_CHG_CFG] = REG_FIELD(0x03, 4, 4),
[F_SYSVMIN] = REG_FIELD(0x03, 1, 3),
/* REG04 */
[F_PUMPX_EN] = REG_FIELD(0x04, 7, 7),
[F_ICHG] = REG_FIELD(0x04, 0, 6),
/* REG05 */
[F_IPRECHG] = REG_FIELD(0x05, 4, 7),
[F_ITERM] = REG_FIELD(0x05, 0, 3),
/* REG06 */
[F_VREG] = REG_FIELD(0x06, 2, 7),
[F_BATLOWV] = REG_FIELD(0x06, 1, 1),
[F_VRECHG] = REG_FIELD(0x06, 0, 0),
/* REG07 */
[F_TERM_EN] = REG_FIELD(0x07, 7, 7),
[F_STAT_DIS] = REG_FIELD(0x07, 6, 6),
[F_WD] = REG_FIELD(0x07, 4, 5),
[F_TMR_EN] = REG_FIELD(0x07, 3, 3),
[F_CHG_TMR] = REG_FIELD(0x07, 1, 2),
[F_JEITA_ISET] = REG_FIELD(0x07, 0, 0),
/* REG08 */
[F_BATCMP] = REG_FIELD(0x08, 6, 7),
[F_VCLAMP] = REG_FIELD(0x08, 2, 4),
[F_TREG] = REG_FIELD(0x08, 0, 1),
/* REG09 */
[F_FORCE_ICO] = REG_FIELD(0x09, 7, 7),
[F_TMR2X_EN] = REG_FIELD(0x09, 6, 6),
[F_BATFET_DIS] = REG_FIELD(0x09, 5, 5),
[F_JEITA_VSET] = REG_FIELD(0x09, 4, 4),
[F_BATFET_DLY] = REG_FIELD(0x09, 3, 3),
[F_BATFET_RST_EN] = REG_FIELD(0x09, 2, 2),
[F_PUMPX_UP] = REG_FIELD(0x09, 1, 1),
[F_PUMPX_DN] = REG_FIELD(0x09, 0, 0),
/* REG0A */
[F_BOOSTV] = REG_FIELD(0x0A, 4, 7),
[F_BOOSTI] = REG_FIELD(0x0A, 0, 2),
/* REG0B */
[F_VBUS_STAT] = REG_FIELD(0x0B, 5, 7),
[F_CHG_STAT] = REG_FIELD(0x0B, 3, 4),
[F_PG_STAT] = REG_FIELD(0x0B, 2, 2),
[F_SDP_STAT] = REG_FIELD(0x0B, 1, 1),
[F_VSYS_STAT] = REG_FIELD(0x0B, 0, 0),
/* REG0C */
[F_WD_FAULT] = REG_FIELD(0x0C, 7, 7),
[F_BOOST_FAULT] = REG_FIELD(0x0C, 6, 6),
[F_CHG_FAULT] = REG_FIELD(0x0C, 4, 5),
[F_BAT_FAULT] = REG_FIELD(0x0C, 3, 3),
[F_NTC_FAULT] = REG_FIELD(0x0C, 0, 2),
/* REG0D */
[F_FORCE_VINDPM] = REG_FIELD(0x0D, 7, 7),
[F_VINDPM] = REG_FIELD(0x0D, 0, 6),
/* REG0E */
[F_THERM_STAT] = REG_FIELD(0x0E, 7, 7),
[F_BATV] = REG_FIELD(0x0E, 0, 6),
/* REG0F */
[F_SYSV] = REG_FIELD(0x0F, 0, 6),
/* REG10 */
[F_TSPCT] = REG_FIELD(0x10, 0, 6),
/* REG11 */
[F_VBUS_GD] = REG_FIELD(0x11, 7, 7),
[F_VBUSV] = REG_FIELD(0x11, 0, 6),
/* REG12 */
[F_ICHGR] = REG_FIELD(0x12, 0, 6),
/* REG13 */
[F_VDPM_STAT] = REG_FIELD(0x13, 7, 7),
[F_IDPM_STAT] = REG_FIELD(0x13, 6, 6),
[F_IDPM_LIM] = REG_FIELD(0x13, 0, 5),
/* REG14 */
[F_REG_RST] = REG_FIELD(0x14, 7, 7),
[F_ICO_OPTIMIZED] = REG_FIELD(0x14, 6, 6),
[F_PN] = REG_FIELD(0x14, 3, 5),
[F_TS_PROFILE] = REG_FIELD(0x14, 2, 2),
[F_DEV_REV] = REG_FIELD(0x14, 0, 1)
};
/*
* Most of the val -> idx conversions can be computed, given the minimum,
* maximum and the step between values. For the rest of conversions, we use
* lookup tables.
*/
enum bq25890_table_ids {
/* range tables */
TBL_ICHG,
TBL_ITERM,
TBL_IPRECHG,
TBL_VREG,
TBL_BATCMP,
TBL_VCLAMP,
TBL_BOOSTV,
TBL_SYSVMIN,
/* lookup tables */
TBL_TREG,
TBL_BOOSTI,
};
/* Thermal Regulation Threshold lookup table, in degrees Celsius */
static const u32 bq25890_treg_tbl[] = { 60, 80, 100, 120 };
#define BQ25890_TREG_TBL_SIZE ARRAY_SIZE(bq25890_treg_tbl)
/* Boost mode current limit lookup table, in uA */
static const u32 bq25890_boosti_tbl[] = {
500000, 700000, 1100000, 1300000, 1600000, 1800000, 2100000, 2400000
};
#define BQ25890_BOOSTI_TBL_SIZE ARRAY_SIZE(bq25890_boosti_tbl)
struct bq25890_range {
u32 min;
u32 max;
u32 step;
};
struct bq25890_lookup {
const u32 *tbl;
u32 size;
};
static const union {
struct bq25890_range rt;
struct bq25890_lookup lt;
} bq25890_tables[] = {
/* range tables */
[TBL_ICHG] = { .rt = {0, 5056000, 64000} }, /* uA */
[TBL_ITERM] = { .rt = {64000, 1024000, 64000} }, /* uA */
[TBL_VREG] = { .rt = {3840000, 4608000, 16000} }, /* uV */
[TBL_BATCMP] = { .rt = {0, 140, 20} }, /* mOhm */
[TBL_VCLAMP] = { .rt = {0, 224000, 32000} }, /* uV */
[TBL_BOOSTV] = { .rt = {4550000, 5510000, 64000} }, /* uV */
[TBL_SYSVMIN] = { .rt = {3000000, 3700000, 100000} }, /* uV */
/* lookup tables */
[TBL_TREG] = { .lt = {bq25890_treg_tbl, BQ25890_TREG_TBL_SIZE} },
[TBL_BOOSTI] = { .lt = {bq25890_boosti_tbl, BQ25890_BOOSTI_TBL_SIZE} }
};
static int bq25890_field_read(struct bq25890_device *bq,
enum bq25890_fields field_id)
{
int ret;
int val;
ret = regmap_field_read(bq->rmap_fields[field_id], &val);
if (ret < 0)
return ret;
return val;
}
static int bq25890_field_write(struct bq25890_device *bq,
enum bq25890_fields field_id, u8 val)
{
return regmap_field_write(bq->rmap_fields[field_id], val);
}
static u8 bq25890_find_idx(u32 value, enum bq25890_table_ids id)
{
u8 idx;
if (id >= TBL_TREG) {
const u32 *tbl = bq25890_tables[id].lt.tbl;
u32 tbl_size = bq25890_tables[id].lt.size;
for (idx = 1; idx < tbl_size && tbl[idx] <= value; idx++)
;
} else {
const struct bq25890_range *rtbl = &bq25890_tables[id].rt;
u8 rtbl_size;
rtbl_size = (rtbl->max - rtbl->min) / rtbl->step + 1;
for (idx = 1;
idx < rtbl_size && (idx * rtbl->step + rtbl->min <= value);
idx++)
;
}
return idx - 1;
}
static u32 bq25890_find_val(u8 idx, enum bq25890_table_ids id)
{
const struct bq25890_range *rtbl;
/* lookup table? */
if (id >= TBL_TREG)
return bq25890_tables[id].lt.tbl[idx];
/* range table */
rtbl = &bq25890_tables[id].rt;
return (rtbl->min + idx * rtbl->step);
}
enum bq25890_status {
STATUS_NOT_CHARGING,
STATUS_PRE_CHARGING,
STATUS_FAST_CHARGING,
STATUS_TERMINATION_DONE,
};
enum bq25890_chrg_fault {
CHRG_FAULT_NORMAL,
CHRG_FAULT_INPUT,
CHRG_FAULT_THERMAL_SHUTDOWN,
CHRG_FAULT_TIMER_EXPIRED,
};
static int bq25890_power_supply_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
int ret;
struct bq25890_device *bq = power_supply_get_drvdata(psy);
struct bq25890_state state;
mutex_lock(&bq->lock);
state = bq->state;
mutex_unlock(&bq->lock);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
if (!state.online)
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
else if (state.chrg_status == STATUS_NOT_CHARGING)
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
else if (state.chrg_status == STATUS_PRE_CHARGING ||
state.chrg_status == STATUS_FAST_CHARGING)
val->intval = POWER_SUPPLY_STATUS_CHARGING;
else if (state.chrg_status == STATUS_TERMINATION_DONE)
val->intval = POWER_SUPPLY_STATUS_FULL;
else
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
break;
case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = BQ25890_MANUFACTURER;
break;
case POWER_SUPPLY_PROP_ONLINE:
val->intval = state.online;
break;
case POWER_SUPPLY_PROP_HEALTH:
if (!state.chrg_fault && !state.bat_fault && !state.boost_fault)
val->intval = POWER_SUPPLY_HEALTH_GOOD;
else if (state.bat_fault)
val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
else if (state.chrg_fault == CHRG_FAULT_TIMER_EXPIRED)
val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
else if (state.chrg_fault == CHRG_FAULT_THERMAL_SHUTDOWN)
val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
else
val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
ret = bq25890_field_read(bq, F_ICHGR); /* read measured value */
if (ret < 0)
return ret;
/* converted_val = ADC_val * 50mA (table 10.3.19) */
val->intval = ret * 50000;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
val->intval = bq25890_tables[TBL_ICHG].rt.max;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
if (!state.online) {
val->intval = 0;
break;
}
ret = bq25890_field_read(bq, F_BATV); /* read measured value */
if (ret < 0)
return ret;
/* converted_val = 2.304V + ADC_val * 20mV (table 10.3.15) */
val->intval = 2304000 + ret * 20000;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
val->intval = bq25890_tables[TBL_VREG].rt.max;
break;
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
val->intval = bq25890_find_val(bq->init_data.iterm, TBL_ITERM);
break;
default:
return -EINVAL;
}
return 0;
}
static int bq25890_get_chip_state(struct bq25890_device *bq,
struct bq25890_state *state)
{
int i, ret;
struct {
enum bq25890_fields id;
u8 *data;
} state_fields[] = {
{F_CHG_STAT, &state->chrg_status},
{F_PG_STAT, &state->online},
{F_VSYS_STAT, &state->vsys_status},
{F_BOOST_FAULT, &state->boost_fault},
{F_BAT_FAULT, &state->bat_fault},
{F_CHG_FAULT, &state->chrg_fault}
};
for (i = 0; i < ARRAY_SIZE(state_fields); i++) {
ret = bq25890_field_read(bq, state_fields[i].id);
if (ret < 0)
return ret;
*state_fields[i].data = ret;
}
dev_dbg(bq->dev, "S:CHG/PG/VSYS=%d/%d/%d, F:CHG/BOOST/BAT=%d/%d/%d\n",
state->chrg_status, state->online, state->vsys_status,
state->chrg_fault, state->boost_fault, state->bat_fault);
return 0;
}
static bool bq25890_state_changed(struct bq25890_device *bq,
struct bq25890_state *new_state)
{
struct bq25890_state old_state;
mutex_lock(&bq->lock);
old_state = bq->state;
mutex_unlock(&bq->lock);
return (old_state.chrg_status != new_state->chrg_status ||
old_state.chrg_fault != new_state->chrg_fault ||
old_state.online != new_state->online ||
old_state.bat_fault != new_state->bat_fault ||
old_state.boost_fault != new_state->boost_fault ||
old_state.vsys_status != new_state->vsys_status);
}
static void bq25890_handle_state_change(struct bq25890_device *bq,
struct bq25890_state *new_state)
{
int ret;
struct bq25890_state old_state;
mutex_lock(&bq->lock);
old_state = bq->state;
mutex_unlock(&bq->lock);
if (!new_state->online) { /* power removed */
/* disable ADC */
ret = bq25890_field_write(bq, F_CONV_START, 0);
if (ret < 0)
goto error;
} else if (!old_state.online) { /* power inserted */
/* enable ADC, to have control of charge current/voltage */
ret = bq25890_field_write(bq, F_CONV_START, 1);
if (ret < 0)
goto error;
}
return;
error:
dev_err(bq->dev, "Error communicating with the chip.\n");
}
static irqreturn_t bq25890_irq_handler_thread(int irq, void *private)
{
struct bq25890_device *bq = private;
int ret;
struct bq25890_state state;
ret = bq25890_get_chip_state(bq, &state);
if (ret < 0)
goto handled;
if (!bq25890_state_changed(bq, &state))
goto handled;
bq25890_handle_state_change(bq, &state);
mutex_lock(&bq->lock);
bq->state = state;
mutex_unlock(&bq->lock);
power_supply_changed(bq->charger);
handled:
return IRQ_HANDLED;
}
static int bq25890_chip_reset(struct bq25890_device *bq)
{
int ret;
int rst_check_counter = 10;
ret = bq25890_field_write(bq, F_REG_RST, 1);
if (ret < 0)
return ret;
do {
ret = bq25890_field_read(bq, F_REG_RST);
if (ret < 0)
return ret;
usleep_range(5, 10);
} while (ret == 1 && --rst_check_counter);
if (!rst_check_counter)
return -ETIMEDOUT;
return 0;
}
static int bq25890_hw_init(struct bq25890_device *bq)
{
int ret;
int i;
struct bq25890_state state;
const struct {
enum bq25890_fields id;
u32 value;
} init_data[] = {
{F_ICHG, bq->init_data.ichg},
{F_VREG, bq->init_data.vreg},
{F_ITERM, bq->init_data.iterm},
{F_IPRECHG, bq->init_data.iprechg},
{F_SYSVMIN, bq->init_data.sysvmin},
{F_BOOSTV, bq->init_data.boostv},
{F_BOOSTI, bq->init_data.boosti},
{F_BOOSTF, bq->init_data.boostf},
{F_EN_ILIM, bq->init_data.ilim_en},
{F_TREG, bq->init_data.treg}
};
ret = bq25890_chip_reset(bq);
if (ret < 0)
return ret;
/* disable watchdog */
ret = bq25890_field_write(bq, F_WD, 0);
if (ret < 0)
return ret;
/* initialize currents/voltages and other parameters */
for (i = 0; i < ARRAY_SIZE(init_data); i++) {
ret = bq25890_field_write(bq, init_data[i].id,
init_data[i].value);
if (ret < 0)
return ret;
}
/* Configure ADC for continuous conversions. This does not enable it. */
ret = bq25890_field_write(bq, F_CONV_RATE, 1);
if (ret < 0)
return ret;
ret = bq25890_get_chip_state(bq, &state);
if (ret < 0)
return ret;
mutex_lock(&bq->lock);
bq->state = state;
mutex_unlock(&bq->lock);
return 0;
}
static enum power_supply_property bq25890_power_supply_props[] = {
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
};
static char *bq25890_charger_supplied_to[] = {
"main-battery",
};
static const struct power_supply_desc bq25890_power_supply_desc = {
.name = "bq25890-charger",
.type = POWER_SUPPLY_TYPE_USB,
.properties = bq25890_power_supply_props,
.num_properties = ARRAY_SIZE(bq25890_power_supply_props),
.get_property = bq25890_power_supply_get_property,
};
static int bq25890_power_supply_init(struct bq25890_device *bq)
{
struct power_supply_config psy_cfg = { .drv_data = bq, };
psy_cfg.supplied_to = bq25890_charger_supplied_to;
psy_cfg.num_supplicants = ARRAY_SIZE(bq25890_charger_supplied_to);
bq->charger = power_supply_register(bq->dev, &bq25890_power_supply_desc,
&psy_cfg);
return PTR_ERR_OR_ZERO(bq->charger);
}
static void bq25890_usb_work(struct work_struct *data)
{
int ret;
struct bq25890_device *bq =
container_of(data, struct bq25890_device, usb_work);
switch (bq->usb_event) {
case USB_EVENT_ID:
/* Enable boost mode */
ret = bq25890_field_write(bq, F_OTG_CFG, 1);
if (ret < 0)
goto error;
break;
case USB_EVENT_NONE:
/* Disable boost mode */
ret = bq25890_field_write(bq, F_OTG_CFG, 0);
if (ret < 0)
goto error;
power_supply_changed(bq->charger);
break;
}
return;
error:
dev_err(bq->dev, "Error switching to boost/charger mode.\n");
}
static int bq25890_usb_notifier(struct notifier_block *nb, unsigned long val,
void *priv)
{
struct bq25890_device *bq =
container_of(nb, struct bq25890_device, usb_nb);
bq->usb_event = val;
queue_work(system_power_efficient_wq, &bq->usb_work);
return NOTIFY_OK;
}
static int bq25890_irq_probe(struct bq25890_device *bq)
{
struct gpio_desc *irq;
irq = devm_gpiod_get_index(bq->dev, BQ25890_IRQ_PIN, 0, GPIOD_IN);
if (IS_ERR(irq)) {
dev_err(bq->dev, "Could not probe irq pin.\n");
return PTR_ERR(irq);
}
return gpiod_to_irq(irq);
}
static int bq25890_fw_read_u32_props(struct bq25890_device *bq)
{
int ret;
u32 property;
int i;
struct bq25890_init_data *init = &bq->init_data;
struct {
char *name;
bool optional;
enum bq25890_table_ids tbl_id;
u8 *conv_data; /* holds converted value from given property */
} props[] = {
/* required properties */
{"ti,charge-current", false, TBL_ICHG, &init->ichg},
{"ti,battery-regulation-voltage", false, TBL_VREG, &init->vreg},
{"ti,termination-current", false, TBL_ITERM, &init->iterm},
{"ti,precharge-current", false, TBL_ITERM, &init->iprechg},
{"ti,minimum-sys-voltage", false, TBL_SYSVMIN, &init->sysvmin},
{"ti,boost-voltage", false, TBL_BOOSTV, &init->boostv},
{"ti,boost-max-current", false, TBL_BOOSTI, &init->boosti},
/* optional properties */
{"ti,thermal-regulation-threshold", true, TBL_TREG, &init->treg}
};
/* initialize data for optional properties */
init->treg = 3; /* 120 degrees Celsius */
for (i = 0; i < ARRAY_SIZE(props); i++) {
ret = device_property_read_u32(bq->dev, props[i].name,
&property);
if (ret < 0) {
if (props[i].optional)
continue;
return ret;
}
*props[i].conv_data = bq25890_find_idx(property,
props[i].tbl_id);
}
return 0;
}
static int bq25890_fw_probe(struct bq25890_device *bq)
{
int ret;
struct bq25890_init_data *init = &bq->init_data;
ret = bq25890_fw_read_u32_props(bq);
if (ret < 0)
return ret;
init->ilim_en = device_property_read_bool(bq->dev, "ti,use-ilim-pin");
init->boostf = device_property_read_bool(bq->dev, "ti,boost-low-freq");
return 0;
}
static int bq25890_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
struct device *dev = &client->dev;
struct bq25890_device *bq;
int ret;
int i;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(dev, "No support for SMBUS_BYTE_DATA\n");
return -ENODEV;
}
bq = devm_kzalloc(dev, sizeof(*bq), GFP_KERNEL);
if (!bq)
return -ENOMEM;
bq->client = client;
bq->dev = dev;
mutex_init(&bq->lock);
bq->rmap = devm_regmap_init_i2c(client, &bq25890_regmap_config);
if (IS_ERR(bq->rmap)) {
dev_err(dev, "failed to allocate register map\n");
return PTR_ERR(bq->rmap);
}
for (i = 0; i < ARRAY_SIZE(bq25890_reg_fields); i++) {
const struct reg_field *reg_fields = bq25890_reg_fields;
bq->rmap_fields[i] = devm_regmap_field_alloc(dev, bq->rmap,
reg_fields[i]);
if (IS_ERR(bq->rmap_fields[i])) {
dev_err(dev, "cannot allocate regmap field\n");
return PTR_ERR(bq->rmap_fields[i]);
}
}
i2c_set_clientdata(client, bq);
bq->chip_id = bq25890_field_read(bq, F_PN);
if (bq->chip_id < 0) {
dev_err(dev, "Cannot read chip ID.\n");
return bq->chip_id;
}
if (bq->chip_id != BQ25890_ID) {
dev_err(dev, "Chip with ID=%d, not supported!\n", bq->chip_id);
return -ENODEV;
}
if (!dev->platform_data) {
ret = bq25890_fw_probe(bq);
if (ret < 0) {
dev_err(dev, "Cannot read device properties.\n");
return ret;
}
} else {
return -ENODEV;
}
ret = bq25890_hw_init(bq);
if (ret < 0) {
dev_err(dev, "Cannot initialize the chip.\n");
return ret;
}
if (client->irq <= 0)
client->irq = bq25890_irq_probe(bq);
if (client->irq < 0) {
dev_err(dev, "No irq resource found.\n");
return client->irq;
}
/* OTG reporting */
bq->usb_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2);
if (!IS_ERR_OR_NULL(bq->usb_phy)) {
INIT_WORK(&bq->usb_work, bq25890_usb_work);
bq->usb_nb.notifier_call = bq25890_usb_notifier;
usb_register_notifier(bq->usb_phy, &bq->usb_nb);
}
ret = devm_request_threaded_irq(dev, client->irq, NULL,
bq25890_irq_handler_thread,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
BQ25890_IRQ_PIN, bq);
if (ret)
goto irq_fail;
ret = bq25890_power_supply_init(bq);
if (ret < 0) {
dev_err(dev, "Failed to register power supply\n");
goto irq_fail;
}
return 0;
irq_fail:
if (!IS_ERR_OR_NULL(bq->usb_phy))
usb_unregister_notifier(bq->usb_phy, &bq->usb_nb);
return ret;
}
static int bq25890_remove(struct i2c_client *client)
{
struct bq25890_device *bq = i2c_get_clientdata(client);
power_supply_unregister(bq->charger);
if (!IS_ERR_OR_NULL(bq->usb_phy))
usb_unregister_notifier(bq->usb_phy, &bq->usb_nb);
/* reset all registers to default values */
bq25890_chip_reset(bq);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int bq25890_suspend(struct device *dev)
{
struct bq25890_device *bq = dev_get_drvdata(dev);
/*
* If charger is removed, while in suspend, make sure ADC is diabled
* since it consumes slightly more power.
*/
return bq25890_field_write(bq, F_CONV_START, 0);
}
static int bq25890_resume(struct device *dev)
{
int ret;
struct bq25890_state state;
struct bq25890_device *bq = dev_get_drvdata(dev);
ret = bq25890_get_chip_state(bq, &state);
if (ret < 0)
return ret;
mutex_lock(&bq->lock);
bq->state = state;
mutex_unlock(&bq->lock);
/* Re-enable ADC only if charger is plugged in. */
if (state.online) {
ret = bq25890_field_write(bq, F_CONV_START, 1);
if (ret < 0)
return ret;
}
/* signal userspace, maybe state changed while suspended */
power_supply_changed(bq->charger);
return 0;
}
#endif
static const struct dev_pm_ops bq25890_pm = {
SET_SYSTEM_SLEEP_PM_OPS(bq25890_suspend, bq25890_resume)
};
static const struct i2c_device_id bq25890_i2c_ids[] = {
{ "bq25890", 0 },
{},
};
MODULE_DEVICE_TABLE(i2c, bq25890_i2c_ids);
static const struct of_device_id bq25890_of_match[] = {
{ .compatible = "ti,bq25890", },
{ },
};
MODULE_DEVICE_TABLE(of, bq25890_of_match);
static const struct acpi_device_id bq25890_acpi_match[] = {
{"BQ258900", 0},
{},
};
MODULE_DEVICE_TABLE(acpi, bq25890_acpi_match);
static struct i2c_driver bq25890_driver = {
.driver = {
.name = "bq25890-charger",
.of_match_table = of_match_ptr(bq25890_of_match),
.acpi_match_table = ACPI_PTR(bq25890_acpi_match),
.pm = &bq25890_pm,
},
.probe = bq25890_probe,
.remove = bq25890_remove,
.id_table = bq25890_i2c_ids,
};
module_i2c_driver(bq25890_driver);
MODULE_AUTHOR("Laurentiu Palcu <laurentiu.palcu@intel.com>");
MODULE_DESCRIPTION("bq25890 charger driver");
MODULE_LICENSE("GPL");
......@@ -1768,7 +1768,8 @@ static int charger_manager_probe(struct platform_device *pdev)
INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk);
cm->charger_psy = power_supply_register(NULL, &cm->charger_psy_desc,
cm->charger_psy = power_supply_register(&pdev->dev,
&cm->charger_psy_desc,
&psy_cfg);
if (IS_ERR(cm->charger_psy)) {
dev_err(&pdev->dev, "Cannot register charger-manager with name \"%s\"\n",
......
......@@ -63,6 +63,8 @@
#define dP_ACC_100 0x1900
#define dP_ACC_200 0x3200
#define MAX17042_VMAX_TOLERANCE 50 /* 50 mV */
struct max17042_chip {
struct i2c_client *client;
struct regmap *regmap;
......@@ -85,10 +87,94 @@ static enum power_supply_property max17042_battery_props[] = {
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_COUNTER,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
POWER_SUPPLY_PROP_TEMP_MIN,
POWER_SUPPLY_PROP_TEMP_MAX,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CURRENT_AVG,
};
static int max17042_get_temperature(struct max17042_chip *chip, int *temp)
{
int ret;
u32 data;
struct regmap *map = chip->regmap;
ret = regmap_read(map, MAX17042_TEMP, &data);
if (ret < 0)
return ret;
*temp = data;
/* The value is signed. */
if (*temp & 0x8000) {
*temp = (0x7fff & ~*temp) + 1;
*temp *= -1;
}
/* The value is converted into deci-centigrade scale */
/* Units of LSB = 1 / 256 degree Celsius */
*temp = *temp * 10 / 256;
return 0;
}
static int max17042_get_battery_health(struct max17042_chip *chip, int *health)
{
int temp, vavg, vbatt, ret;
u32 val;
ret = regmap_read(chip->regmap, MAX17042_AvgVCELL, &val);
if (ret < 0)
goto health_error;
/* bits [0-3] unused */
vavg = val * 625 / 8;
/* Convert to millivolts */
vavg /= 1000;
ret = regmap_read(chip->regmap, MAX17042_VCELL, &val);
if (ret < 0)
goto health_error;
/* bits [0-3] unused */
vbatt = val * 625 / 8;
/* Convert to millivolts */
vbatt /= 1000;
if (vavg < chip->pdata->vmin) {
*health = POWER_SUPPLY_HEALTH_DEAD;
goto out;
}
if (vbatt > chip->pdata->vmax + MAX17042_VMAX_TOLERANCE) {
*health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
goto out;
}
ret = max17042_get_temperature(chip, &temp);
if (ret < 0)
goto health_error;
if (temp <= chip->pdata->temp_min) {
*health = POWER_SUPPLY_HEALTH_COLD;
goto out;
}
if (temp >= chip->pdata->temp_max) {
*health = POWER_SUPPLY_HEALTH_OVERHEAT;
goto out;
}
*health = POWER_SUPPLY_HEALTH_GOOD;
out:
return 0;
health_error:
return ret;
}
static int max17042_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
......@@ -181,19 +267,34 @@ static int max17042_get_property(struct power_supply *psy,
val->intval = data * 1000 / 2;
break;
case POWER_SUPPLY_PROP_TEMP:
ret = regmap_read(map, MAX17042_TEMP, &data);
ret = max17042_get_temperature(chip, &val->intval);
if (ret < 0)
return ret;
break;
case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
ret = regmap_read(map, MAX17042_TALRT_Th, &data);
if (ret < 0)
return ret;
/* LSB is Alert Minimum. In deci-centigrade */
val->intval = (data & 0xff) * 10;
break;
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
ret = regmap_read(map, MAX17042_TALRT_Th, &data);
if (ret < 0)
return ret;
/* MSB is Alert Maximum. In deci-centigrade */
val->intval = (data >> 8) * 10;
break;
case POWER_SUPPLY_PROP_TEMP_MIN:
val->intval = chip->pdata->temp_min;
break;
case POWER_SUPPLY_PROP_TEMP_MAX:
val->intval = chip->pdata->temp_max;
break;
case POWER_SUPPLY_PROP_HEALTH:
ret = max17042_get_battery_health(chip, &val->intval);
if (ret < 0)
return ret;
val->intval = data;
/* The value is signed. */
if (val->intval & 0x8000) {
val->intval = (0x7fff & ~val->intval) + 1;
val->intval *= -1;
}
/* The value is converted into deci-centigrade scale */
/* Units of LSB = 1 / 256 degree Celsius */
val->intval = val->intval * 10 / 256;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
if (chip->pdata->enable_current_sense) {
......@@ -237,6 +338,69 @@ static int max17042_get_property(struct power_supply *psy,
return 0;
}
static int max17042_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct max17042_chip *chip = power_supply_get_drvdata(psy);
struct regmap *map = chip->regmap;
int ret = 0;
u32 data;
int8_t temp;
switch (psp) {
case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
ret = regmap_read(map, MAX17042_TALRT_Th, &data);
if (ret < 0)
return ret;
/* Input in deci-centigrade, convert to centigrade */
temp = val->intval / 10;
/* force min < max */
if (temp >= (int8_t)(data >> 8))
temp = (int8_t)(data >> 8) - 1;
/* Write both MAX and MIN ALERT */
data = (data & 0xff00) + temp;
ret = regmap_write(map, MAX17042_TALRT_Th, data);
break;
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
ret = regmap_read(map, MAX17042_TALRT_Th, &data);
if (ret < 0)
return ret;
/* Input in Deci-Centigrade, convert to centigrade */
temp = val->intval / 10;
/* force max > min */
if (temp <= (int8_t)(data & 0xff))
temp = (int8_t)(data & 0xff) + 1;
/* Write both MAX and MIN ALERT */
data = (data & 0xff) + (temp << 8);
ret = regmap_write(map, MAX17042_TALRT_Th, data);
break;
default:
ret = -EINVAL;
}
return ret;
}
static int max17042_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
int ret;
switch (psp) {
case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
ret = 1;
break;
default:
ret = 0;
}
return ret;
}
static int max17042_write_verify_reg(struct regmap *map, u8 reg, u32 value)
{
int retries = 8;
......@@ -645,6 +809,15 @@ max17042_get_pdata(struct device *dev)
pdata->enable_current_sense = true;
}
if (of_property_read_s32(np, "maxim,cold-temp", &pdata->temp_min))
pdata->temp_min = INT_MIN;
if (of_property_read_s32(np, "maxim,over-heat-temp", &pdata->temp_max))
pdata->temp_max = INT_MAX;
if (of_property_read_s32(np, "maxim,dead-volt", &pdata->vmin))
pdata->vmin = INT_MIN;
if (of_property_read_s32(np, "maxim,over-volt", &pdata->vmax))
pdata->vmax = INT_MAX;
return pdata;
}
#else
......@@ -665,6 +838,8 @@ static const struct power_supply_desc max17042_psy_desc = {
.name = "max170xx_battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.get_property = max17042_get_property,
.set_property = max17042_set_property,
.property_is_writeable = max17042_property_is_writeable,
.properties = max17042_battery_props,
.num_properties = ARRAY_SIZE(max17042_battery_props),
};
......@@ -673,6 +848,8 @@ static const struct power_supply_desc max17042_no_current_sense_psy_desc = {
.name = "max170xx_battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.get_property = max17042_get_property,
.set_property = max17042_set_property,
.property_is_writeable = max17042_property_is_writeable,
.properties = max17042_battery_props,
.num_properties = ARRAY_SIZE(max17042_battery_props) - 2,
};
......
......@@ -30,6 +30,8 @@ EXPORT_SYMBOL_GPL(power_supply_notifier);
static struct device_type power_supply_dev_type;
#define POWER_SUPPLY_DEFERRED_REGISTER_TIME msecs_to_jiffies(10)
static bool __power_supply_is_supplied_by(struct power_supply *supplier,
struct power_supply *supply)
{
......@@ -121,6 +123,30 @@ void power_supply_changed(struct power_supply *psy)
}
EXPORT_SYMBOL_GPL(power_supply_changed);
/*
* Notify that power supply was registered after parent finished the probing.
*
* Often power supply is registered from driver's probe function. However
* calling power_supply_changed() directly from power_supply_register()
* would lead to execution of get_property() function provided by the driver
* too early - before the probe ends.
*
* Avoid that by waiting on parent's mutex.
*/
static void power_supply_deferred_register_work(struct work_struct *work)
{
struct power_supply *psy = container_of(work, struct power_supply,
deferred_register_work.work);
if (psy->dev.parent)
mutex_lock(&psy->dev.parent->mutex);
power_supply_changed(psy);
if (psy->dev.parent)
mutex_unlock(&psy->dev.parent->mutex);
}
#ifdef CONFIG_OF
#include <linux/of.h>
......@@ -420,6 +446,45 @@ struct power_supply *power_supply_get_by_phandle(struct device_node *np,
return psy;
}
EXPORT_SYMBOL_GPL(power_supply_get_by_phandle);
static void devm_power_supply_put(struct device *dev, void *res)
{
struct power_supply **psy = res;
power_supply_put(*psy);
}
/**
* devm_power_supply_get_by_phandle() - Resource managed version of
* power_supply_get_by_phandle()
* @dev: Pointer to device holding phandle property
* @phandle_name: Name of property holding a power supply phandle
*
* Return: On success returns a reference to a power supply with
* matching name equals to value under @property, NULL or ERR_PTR otherwise.
*/
struct power_supply *devm_power_supply_get_by_phandle(struct device *dev,
const char *property)
{
struct power_supply **ptr, *psy;
if (!dev->of_node)
return ERR_PTR(-ENODEV);
ptr = devres_alloc(devm_power_supply_put, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
psy = power_supply_get_by_phandle(dev->of_node, property);
if (IS_ERR_OR_NULL(psy)) {
devres_free(ptr);
} else {
*ptr = psy;
devres_add(dev, ptr);
}
return psy;
}
EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle);
#endif /* CONFIG_OF */
int power_supply_get_property(struct power_supply *psy,
......@@ -645,6 +710,10 @@ __power_supply_register(struct device *parent,
struct power_supply *psy;
int rc;
if (!parent)
pr_warn("%s: Expected proper parent device for '%s'\n",
__func__, desc->name);
psy = kzalloc(sizeof(*psy), GFP_KERNEL);
if (!psy)
return ERR_PTR(-ENOMEM);
......@@ -659,7 +728,6 @@ __power_supply_register(struct device *parent,
dev->release = power_supply_dev_release;
dev_set_drvdata(dev, psy);
psy->desc = desc;
atomic_inc(&psy->use_cnt);
if (cfg) {
psy->drv_data = cfg->drv_data;
psy->of_node = cfg->of_node;
......@@ -672,6 +740,8 @@ __power_supply_register(struct device *parent,
goto dev_set_name_failed;
INIT_WORK(&psy->changed_work, power_supply_changed_work);
INIT_DELAYED_WORK(&psy->deferred_register_work,
power_supply_deferred_register_work);
rc = power_supply_check_supplies(psy);
if (rc) {
......@@ -700,7 +770,20 @@ __power_supply_register(struct device *parent,
if (rc)
goto create_triggers_failed;
power_supply_changed(psy);
/*
* Update use_cnt after any uevents (most notably from device_add()).
* We are here still during driver's probe but
* the power_supply_uevent() calls back driver's get_property
* method so:
* 1. Driver did not assigned the returned struct power_supply,
* 2. Driver could not finish initialization (anything in its probe
* after calling power_supply_register()).
*/
atomic_inc(&psy->use_cnt);
queue_delayed_work(system_power_efficient_wq,
&psy->deferred_register_work,
POWER_SUPPLY_DEFERRED_REGISTER_TIME);
return psy;
......@@ -720,7 +803,8 @@ __power_supply_register(struct device *parent,
/**
* power_supply_register() - Register new power supply
* @parent: Device to be a parent of power supply's device
* @parent: Device to be a parent of power supply's device, usually
* the device which probe function calls this
* @desc: Description of power supply, must be valid through whole
* lifetime of this power supply
* @cfg: Run-time specific configuration accessed during registering,
......@@ -740,8 +824,9 @@ struct power_supply *__must_check power_supply_register(struct device *parent,
EXPORT_SYMBOL_GPL(power_supply_register);
/**
* power_supply_register() - Register new non-waking-source power supply
* @parent: Device to be a parent of power supply's device
* power_supply_register_no_ws() - Register new non-waking-source power supply
* @parent: Device to be a parent of power supply's device, usually
* the device which probe function calls this
* @desc: Description of power supply, must be valid through whole
* lifetime of this power supply
* @cfg: Run-time specific configuration accessed during registering,
......@@ -769,8 +854,9 @@ static void devm_power_supply_release(struct device *dev, void *res)
}
/**
* power_supply_register() - Register managed power supply
* @parent: Device to be a parent of power supply's device
* devm_power_supply_register() - Register managed power supply
* @parent: Device to be a parent of power supply's device, usually
* the device which probe function calls this
* @desc: Description of power supply, must be valid through whole
* lifetime of this power supply
* @cfg: Run-time specific configuration accessed during registering,
......@@ -804,8 +890,9 @@ devm_power_supply_register(struct device *parent,
EXPORT_SYMBOL_GPL(devm_power_supply_register);
/**
* power_supply_register() - Register managed non-waking-source power supply
* @parent: Device to be a parent of power supply's device
* devm_power_supply_register_no_ws() - Register managed non-waking-source power supply
* @parent: Device to be a parent of power supply's device, usually
* the device which probe function calls this
* @desc: Description of power supply, must be valid through whole
* lifetime of this power supply
* @cfg: Run-time specific configuration accessed during registering,
......@@ -849,6 +936,7 @@ void power_supply_unregister(struct power_supply *psy)
{
WARN_ON(atomic_dec_return(&psy->use_cnt));
cancel_work_sync(&psy->changed_work);
cancel_delayed_work_sync(&psy->deferred_register_work);
sysfs_remove_link(&psy->dev.kobj, "powers");
power_supply_remove_triggers(psy);
psy_unregister_cooler(psy);
......
......@@ -25,7 +25,7 @@ static void power_supply_update_bat_leds(struct power_supply *psy)
unsigned long delay_on = 0;
unsigned long delay_off = 0;
if (psy->desc->get_property(psy, POWER_SUPPLY_PROP_STATUS, &status))
if (power_supply_get_property(psy, POWER_SUPPLY_PROP_STATUS, &status))
return;
dev_dbg(&psy->dev, "%s %d\n", __func__, status.intval);
......@@ -115,7 +115,7 @@ static void power_supply_update_gen_leds(struct power_supply *psy)
{
union power_supply_propval online;
if (psy->desc->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &online))
if (power_supply_get_property(psy, POWER_SUPPLY_PROP_ONLINE, &online))
return;
dev_dbg(&psy->dev, "%s %d\n", __func__, online.intval);
......
......@@ -125,7 +125,7 @@ static ssize_t power_supply_store_property(struct device *dev,
value.intval = long_val;
ret = psy->desc->set_property(psy, off, &value);
ret = power_supply_set_property(psy, off, &value);
if (ret < 0)
return ret;
......@@ -223,7 +223,7 @@ static umode_t power_supply_attr_is_visible(struct kobject *kobj,
if (property == attrno) {
if (psy->desc->property_is_writeable &&
power_supply_property_is_writeable(psy, property) > 0)
psy->desc->property_is_writeable(psy, property) > 0)
mode |= S_IWUSR;
return mode;
......
......@@ -243,7 +243,7 @@ static int at91_reset_probe(struct platform_device *pdev)
return 0;
}
static struct platform_device_id at91_reset_plat_match[] = {
static const struct platform_device_id at91_reset_plat_match[] = {
{ "at91-sam9260-reset", (unsigned long)at91sam9260_restart },
{ "at91-sam9g45-reset", (unsigned long)at91sam9g45_restart },
{ /* sentinel */ }
......
......@@ -48,6 +48,7 @@ static void gpio_poweroff_do_poweroff(void)
static int gpio_poweroff_probe(struct platform_device *pdev)
{
bool input = false;
enum gpiod_flags flags;
/* If a pm_power_off function has already been added, leave it alone */
if (pm_power_off != NULL) {
......@@ -57,25 +58,15 @@ static int gpio_poweroff_probe(struct platform_device *pdev)
return -EBUSY;
}
reset_gpio = devm_gpiod_get(&pdev->dev, NULL);
if (IS_ERR(reset_gpio))
return PTR_ERR(reset_gpio);
input = of_property_read_bool(pdev->dev.of_node, "input");
if (input)
flags = GPIOD_IN;
else
flags = GPIOD_OUT_LOW;
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;
}
}
reset_gpio = devm_gpiod_get(&pdev->dev, NULL, flags);
if (IS_ERR(reset_gpio))
return PTR_ERR(reset_gpio);
pm_power_off = &gpio_poweroff_do_poweroff;
return 0;
......
......@@ -78,7 +78,7 @@ static int gpio_restart_probe(struct platform_device *pdev)
}
gpio_restart->restart_handler.notifier_call = gpio_restart_notify;
gpio_restart->restart_handler.priority = 128;
gpio_restart->restart_handler.priority = 129;
gpio_restart->active_delay_ms = 100;
gpio_restart->inactive_delay_ms = 100;
gpio_restart->wait_delay_ms = 3000;
......
......@@ -201,16 +201,15 @@ static int ltc2952_poweroff_init(struct platform_device *pdev)
return ret;
}
data->gpio_trigger = devm_gpiod_get(&pdev->dev, "trigger", GPIOD_IN);
data->gpio_trigger = devm_gpiod_get_optional(&pdev->dev, "trigger",
GPIOD_IN);
if (IS_ERR(data->gpio_trigger)) {
/*
* It's not a problem if the trigger gpio isn't available, but
* it is worth a warning if its use was defined in the device
* tree.
*/
if (PTR_ERR(data->gpio_trigger) != -ENOENT)
dev_err(&pdev->dev,
"unable to claim gpio \"trigger\"\n");
dev_err(&pdev->dev, "unable to claim gpio \"trigger\"\n");
data->gpio_trigger = NULL;
}
......
/*
* Driver for Richtek RT9455WSC battery charger.
*
* 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/interrupt.h>
#include <linux/delay.h>
#include <linux/of_irq.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <linux/power_supply.h>
#include <linux/i2c.h>
#include <linux/acpi.h>
#include <linux/usb/phy.h>
#include <linux/regmap.h>
#define RT9455_MANUFACTURER "Richtek"
#define RT9455_MODEL_NAME "RT9455"
#define RT9455_DRIVER_NAME "rt9455-charger"
#define RT9455_IRQ_NAME "interrupt"
#define RT9455_PWR_RDY_DELAY 1 /* 1 second */
#define RT9455_MAX_CHARGING_TIME 21600 /* 6 hrs */
#define RT9455_BATT_PRESENCE_DELAY 60 /* 60 seconds */
#define RT9455_CHARGE_MODE 0x00
#define RT9455_BOOST_MODE 0x01
#define RT9455_FAULT 0x03
#define RT9455_IAICR_100MA 0x00
#define RT9455_IAICR_500MA 0x01
#define RT9455_IAICR_NO_LIMIT 0x03
#define RT9455_CHARGE_DISABLE 0x00
#define RT9455_CHARGE_ENABLE 0x01
#define RT9455_PWR_FAULT 0x00
#define RT9455_PWR_GOOD 0x01
#define RT9455_REG_CTRL1 0x00 /* CTRL1 reg address */
#define RT9455_REG_CTRL2 0x01 /* CTRL2 reg address */
#define RT9455_REG_CTRL3 0x02 /* CTRL3 reg address */
#define RT9455_REG_DEV_ID 0x03 /* DEV_ID reg address */
#define RT9455_REG_CTRL4 0x04 /* CTRL4 reg address */
#define RT9455_REG_CTRL5 0x05 /* CTRL5 reg address */
#define RT9455_REG_CTRL6 0x06 /* CTRL6 reg address */
#define RT9455_REG_CTRL7 0x07 /* CTRL7 reg address */
#define RT9455_REG_IRQ1 0x08 /* IRQ1 reg address */
#define RT9455_REG_IRQ2 0x09 /* IRQ2 reg address */
#define RT9455_REG_IRQ3 0x0A /* IRQ3 reg address */
#define RT9455_REG_MASK1 0x0B /* MASK1 reg address */
#define RT9455_REG_MASK2 0x0C /* MASK2 reg address */
#define RT9455_REG_MASK3 0x0D /* MASK3 reg address */
enum rt9455_fields {
F_STAT, F_BOOST, F_PWR_RDY, F_OTG_PIN_POLARITY, /* CTRL1 reg fields */
F_IAICR, F_TE_SHDN_EN, F_HIGHER_OCP, F_TE, F_IAICR_INT, F_HIZ,
F_OPA_MODE, /* CTRL2 reg fields */
F_VOREG, F_OTG_PL, F_OTG_EN, /* CTRL3 reg fields */
F_VENDOR_ID, F_CHIP_REV, /* DEV_ID reg fields */
F_RST, /* CTRL4 reg fields */
F_TMR_EN, F_MIVR, F_IPREC, F_IEOC_PERCENTAGE, /* CTRL5 reg fields*/
F_IAICR_SEL, F_ICHRG, F_VPREC, /* CTRL6 reg fields */
F_BATD_EN, F_CHG_EN, F_VMREG, /* CTRL7 reg fields */
F_TSDI, F_VINOVPI, F_BATAB, /* IRQ1 reg fields */
F_CHRVPI, F_CHBATOVI, F_CHTERMI, F_CHRCHGI, F_CH32MI, F_CHTREGI,
F_CHMIVRI, /* IRQ2 reg fields */
F_BSTBUSOVI, F_BSTOLI, F_BSTLOWVI, F_BST32SI, /* IRQ3 reg fields */
F_TSDM, F_VINOVPIM, F_BATABM, /* MASK1 reg fields */
F_CHRVPIM, F_CHBATOVIM, F_CHTERMIM, F_CHRCHGIM, F_CH32MIM, F_CHTREGIM,
F_CHMIVRIM, /* MASK2 reg fields */
F_BSTVINOVIM, F_BSTOLIM, F_BSTLOWVIM, F_BST32SIM, /* MASK3 reg fields */
F_MAX_FIELDS
};
static const struct reg_field rt9455_reg_fields[] = {
[F_STAT] = REG_FIELD(RT9455_REG_CTRL1, 4, 5),
[F_BOOST] = REG_FIELD(RT9455_REG_CTRL1, 3, 3),
[F_PWR_RDY] = REG_FIELD(RT9455_REG_CTRL1, 2, 2),
[F_OTG_PIN_POLARITY] = REG_FIELD(RT9455_REG_CTRL1, 1, 1),
[F_IAICR] = REG_FIELD(RT9455_REG_CTRL2, 6, 7),
[F_TE_SHDN_EN] = REG_FIELD(RT9455_REG_CTRL2, 5, 5),
[F_HIGHER_OCP] = REG_FIELD(RT9455_REG_CTRL2, 4, 4),
[F_TE] = REG_FIELD(RT9455_REG_CTRL2, 3, 3),
[F_IAICR_INT] = REG_FIELD(RT9455_REG_CTRL2, 2, 2),
[F_HIZ] = REG_FIELD(RT9455_REG_CTRL2, 1, 1),
[F_OPA_MODE] = REG_FIELD(RT9455_REG_CTRL2, 0, 0),
[F_VOREG] = REG_FIELD(RT9455_REG_CTRL3, 2, 7),
[F_OTG_PL] = REG_FIELD(RT9455_REG_CTRL3, 1, 1),
[F_OTG_EN] = REG_FIELD(RT9455_REG_CTRL3, 0, 0),
[F_VENDOR_ID] = REG_FIELD(RT9455_REG_DEV_ID, 4, 7),
[F_CHIP_REV] = REG_FIELD(RT9455_REG_DEV_ID, 0, 3),
[F_RST] = REG_FIELD(RT9455_REG_CTRL4, 7, 7),
[F_TMR_EN] = REG_FIELD(RT9455_REG_CTRL5, 7, 7),
[F_MIVR] = REG_FIELD(RT9455_REG_CTRL5, 4, 5),
[F_IPREC] = REG_FIELD(RT9455_REG_CTRL5, 2, 3),
[F_IEOC_PERCENTAGE] = REG_FIELD(RT9455_REG_CTRL5, 0, 1),
[F_IAICR_SEL] = REG_FIELD(RT9455_REG_CTRL6, 7, 7),
[F_ICHRG] = REG_FIELD(RT9455_REG_CTRL6, 4, 6),
[F_VPREC] = REG_FIELD(RT9455_REG_CTRL6, 0, 2),
[F_BATD_EN] = REG_FIELD(RT9455_REG_CTRL7, 6, 6),
[F_CHG_EN] = REG_FIELD(RT9455_REG_CTRL7, 4, 4),
[F_VMREG] = REG_FIELD(RT9455_REG_CTRL7, 0, 3),
[F_TSDI] = REG_FIELD(RT9455_REG_IRQ1, 7, 7),
[F_VINOVPI] = REG_FIELD(RT9455_REG_IRQ1, 6, 6),
[F_BATAB] = REG_FIELD(RT9455_REG_IRQ1, 0, 0),
[F_CHRVPI] = REG_FIELD(RT9455_REG_IRQ2, 7, 7),
[F_CHBATOVI] = REG_FIELD(RT9455_REG_IRQ2, 5, 5),
[F_CHTERMI] = REG_FIELD(RT9455_REG_IRQ2, 4, 4),
[F_CHRCHGI] = REG_FIELD(RT9455_REG_IRQ2, 3, 3),
[F_CH32MI] = REG_FIELD(RT9455_REG_IRQ2, 2, 2),
[F_CHTREGI] = REG_FIELD(RT9455_REG_IRQ2, 1, 1),
[F_CHMIVRI] = REG_FIELD(RT9455_REG_IRQ2, 0, 0),
[F_BSTBUSOVI] = REG_FIELD(RT9455_REG_IRQ3, 7, 7),
[F_BSTOLI] = REG_FIELD(RT9455_REG_IRQ3, 6, 6),
[F_BSTLOWVI] = REG_FIELD(RT9455_REG_IRQ3, 5, 5),
[F_BST32SI] = REG_FIELD(RT9455_REG_IRQ3, 3, 3),
[F_TSDM] = REG_FIELD(RT9455_REG_MASK1, 7, 7),
[F_VINOVPIM] = REG_FIELD(RT9455_REG_MASK1, 6, 6),
[F_BATABM] = REG_FIELD(RT9455_REG_MASK1, 0, 0),
[F_CHRVPIM] = REG_FIELD(RT9455_REG_MASK2, 7, 7),
[F_CHBATOVIM] = REG_FIELD(RT9455_REG_MASK2, 5, 5),
[F_CHTERMIM] = REG_FIELD(RT9455_REG_MASK2, 4, 4),
[F_CHRCHGIM] = REG_FIELD(RT9455_REG_MASK2, 3, 3),
[F_CH32MIM] = REG_FIELD(RT9455_REG_MASK2, 2, 2),
[F_CHTREGIM] = REG_FIELD(RT9455_REG_MASK2, 1, 1),
[F_CHMIVRIM] = REG_FIELD(RT9455_REG_MASK2, 0, 0),
[F_BSTVINOVIM] = REG_FIELD(RT9455_REG_MASK3, 7, 7),
[F_BSTOLIM] = REG_FIELD(RT9455_REG_MASK3, 6, 6),
[F_BSTLOWVIM] = REG_FIELD(RT9455_REG_MASK3, 5, 5),
[F_BST32SIM] = REG_FIELD(RT9455_REG_MASK3, 3, 3),
};
#define GET_MASK(fid) (BIT(rt9455_reg_fields[fid].msb + 1) - \
BIT(rt9455_reg_fields[fid].lsb))
/*
* Each array initialised below shows the possible real-world values for a
* group of bits belonging to RT9455 registers. The arrays are sorted in
* ascending order. The index of each real-world value represents the value
* that is encoded in the group of bits belonging to RT9455 registers.
*/
/* REG06[6:4] (ICHRG) in uAh */
static const int rt9455_ichrg_values[] = {
500000, 650000, 800000, 950000, 1100000, 1250000, 1400000, 1550000
};
/*
* When the charger is in charge mode, REG02[7:2] represent battery regulation
* voltage.
*/
/* REG02[7:2] (VOREG) in uV */
static const int rt9455_voreg_values[] = {
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, 4330000, 4350000, 4370000, 4390000, 4410000, 4430000, 4450000,
4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000,
4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000
};
/*
* When the charger is in boost mode, REG02[7:2] represent boost output
* voltage.
*/
/* REG02[7:2] (Boost output voltage) in uV */
static const int rt9455_boost_voltage_values[] = {
4425000, 4450000, 4475000, 4500000, 4525000, 4550000, 4575000, 4600000,
4625000, 4650000, 4675000, 4700000, 4725000, 4750000, 4775000, 4800000,
4825000, 4850000, 4875000, 4900000, 4925000, 4950000, 4975000, 5000000,
5025000, 5050000, 5075000, 5100000, 5125000, 5150000, 5175000, 5200000,
5225000, 5250000, 5275000, 5300000, 5325000, 5350000, 5375000, 5400000,
5425000, 5450000, 5475000, 5500000, 5525000, 5550000, 5575000, 5600000,
5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000,
5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000,
};
/* REG07[3:0] (VMREG) in uV */
static const int rt9455_vmreg_values[] = {
4200000, 4220000, 4240000, 4260000, 4280000, 4300000, 4320000, 4340000,
4360000, 4380000, 4400000, 4430000, 4450000, 4450000, 4450000, 4450000
};
/* REG05[5:4] (IEOC_PERCENTAGE) */
static const int rt9455_ieoc_percentage_values[] = {
10, 30, 20, 30
};
/* REG05[1:0] (MIVR) in uV */
static const int rt9455_mivr_values[] = {
4000000, 4250000, 4500000, 5000000
};
/* REG05[1:0] (IAICR) in uA */
static const int rt9455_iaicr_values[] = {
100000, 500000, 1000000, 2000000
};
struct rt9455_info {
struct i2c_client *client;
struct regmap *regmap;
struct regmap_field *regmap_fields[F_MAX_FIELDS];
struct power_supply *charger;
#if IS_ENABLED(CONFIG_USB_PHY)
struct usb_phy *usb_phy;
struct notifier_block nb;
#endif
struct delayed_work pwr_rdy_work;
struct delayed_work max_charging_time_work;
struct delayed_work batt_presence_work;
u32 voreg;
u32 boost_voltage;
};
/*
* Iterate through each element of the 'tbl' array until an element whose value
* is greater than v is found. Return the index of the respective element,
* or the index of the last element in the array, if no such element is found.
*/
static unsigned int rt9455_find_idx(const int tbl[], int tbl_size, int v)
{
int i;
/*
* No need to iterate until the last index in the table because
* if no element greater than v is found in the table,
* or if only the last element is greater than v,
* function returns the index of the last element.
*/
for (i = 0; i < tbl_size - 1; i++)
if (v <= tbl[i])
return i;
return (tbl_size - 1);
}
static int rt9455_get_field_val(struct rt9455_info *info,
enum rt9455_fields field,
const int tbl[], int tbl_size, int *val)
{
unsigned int v;
int ret;
ret = regmap_field_read(info->regmap_fields[field], &v);
if (ret)
return ret;
v = (v >= tbl_size) ? (tbl_size - 1) : v;
*val = tbl[v];
return 0;
}
static int rt9455_set_field_val(struct rt9455_info *info,
enum rt9455_fields field,
const int tbl[], int tbl_size, int val)
{
unsigned int idx = rt9455_find_idx(tbl, tbl_size, val);
return regmap_field_write(info->regmap_fields[field], idx);
}
static int rt9455_register_reset(struct rt9455_info *info)
{
struct device *dev = &info->client->dev;
unsigned int v;
int ret, limit = 100;
ret = regmap_field_write(info->regmap_fields[F_RST], 0x01);
if (ret) {
dev_err(dev, "Failed to set RST bit\n");
return ret;
}
/*
* To make sure that reset operation has finished, loop until RST bit
* is set to 0.
*/
do {
ret = regmap_field_read(info->regmap_fields[F_RST], &v);
if (ret) {
dev_err(dev, "Failed to read RST bit\n");
return ret;
}
if (!v)
break;
usleep_range(10, 100);
} while (--limit);
if (!limit)
return -EIO;
return 0;
}
/* Charger power supply property routines */
static enum power_supply_property rt9455_charger_properties[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_ONLINE,
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_SCOPE,
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
};
static char *rt9455_charger_supplied_to[] = {
"main-battery",
};
static int rt9455_charger_get_status(struct rt9455_info *info,
union power_supply_propval *val)
{
unsigned int v, pwr_rdy;
int ret;
ret = regmap_field_read(info->regmap_fields[F_PWR_RDY],
&pwr_rdy);
if (ret) {
dev_err(&info->client->dev, "Failed to read PWR_RDY bit\n");
return ret;
}
/*
* If PWR_RDY bit is unset, the battery is discharging. Otherwise,
* STAT bits value must be checked.
*/
if (!pwr_rdy) {
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
return 0;
}
ret = regmap_field_read(info->regmap_fields[F_STAT], &v);
if (ret) {
dev_err(&info->client->dev, "Failed to read STAT bits\n");
return ret;
}
switch (v) {
case 0:
/*
* If PWR_RDY bit is set, but STAT bits value is 0, the charger
* may be in one of the following cases:
* 1. CHG_EN bit is 0.
* 2. CHG_EN bit is 1 but the battery is not connected.
* In any of these cases, POWER_SUPPLY_STATUS_NOT_CHARGING is
* returned.
*/
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
return 0;
case 1:
val->intval = POWER_SUPPLY_STATUS_CHARGING;
return 0;
case 2:
val->intval = POWER_SUPPLY_STATUS_FULL;
return 0;
default:
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
return 0;
}
}
static int rt9455_charger_get_health(struct rt9455_info *info,
union power_supply_propval *val)
{
struct device *dev = &info->client->dev;
unsigned int v;
int ret;
val->intval = POWER_SUPPLY_HEALTH_GOOD;
ret = regmap_read(info->regmap, RT9455_REG_IRQ1, &v);
if (ret) {
dev_err(dev, "Failed to read IRQ1 register\n");
return ret;
}
if (v & GET_MASK(F_TSDI)) {
val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
return 0;
}
if (v & GET_MASK(F_VINOVPI)) {
val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
return 0;
}
if (v & GET_MASK(F_BATAB)) {
val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
return 0;
}
ret = regmap_read(info->regmap, RT9455_REG_IRQ2, &v);
if (ret) {
dev_err(dev, "Failed to read IRQ2 register\n");
return ret;
}
if (v & GET_MASK(F_CHBATOVI)) {
val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
return 0;
}
if (v & GET_MASK(F_CH32MI)) {
val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
return 0;
}
ret = regmap_read(info->regmap, RT9455_REG_IRQ3, &v);
if (ret) {
dev_err(dev, "Failed to read IRQ3 register\n");
return ret;
}
if (v & GET_MASK(F_BSTBUSOVI)) {
val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
return 0;
}
if (v & GET_MASK(F_BSTOLI)) {
val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
return 0;
}
if (v & GET_MASK(F_BSTLOWVI)) {
val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
return 0;
}
if (v & GET_MASK(F_BST32SI)) {
val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
return 0;
}
ret = regmap_field_read(info->regmap_fields[F_STAT], &v);
if (ret) {
dev_err(dev, "Failed to read STAT bits\n");
return ret;
}
if (v == RT9455_FAULT) {
val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
return 0;
}
return 0;
}
static int rt9455_charger_get_battery_presence(struct rt9455_info *info,
union power_supply_propval *val)
{
unsigned int v;
int ret;
ret = regmap_field_read(info->regmap_fields[F_BATAB], &v);
if (ret) {
dev_err(&info->client->dev, "Failed to read BATAB bit\n");
return ret;
}
/*
* Since BATAB is 1 when battery is NOT present and 0 otherwise,
* !BATAB is returned.
*/
val->intval = !v;
return 0;
}
static int rt9455_charger_get_online(struct rt9455_info *info,
union power_supply_propval *val)
{
unsigned int v;
int ret;
ret = regmap_field_read(info->regmap_fields[F_PWR_RDY], &v);
if (ret) {
dev_err(&info->client->dev, "Failed to read PWR_RDY bit\n");
return ret;
}
val->intval = (int)v;
return 0;
}
static int rt9455_charger_get_current(struct rt9455_info *info,
union power_supply_propval *val)
{
int curr;
int ret;
ret = rt9455_get_field_val(info, F_ICHRG,
rt9455_ichrg_values,
ARRAY_SIZE(rt9455_ichrg_values),
&curr);
if (ret) {
dev_err(&info->client->dev, "Failed to read ICHRG value\n");
return ret;
}
val->intval = curr;
return 0;
}
static int rt9455_charger_get_current_max(struct rt9455_info *info,
union power_supply_propval *val)
{
int idx = ARRAY_SIZE(rt9455_ichrg_values) - 1;
val->intval = rt9455_ichrg_values[idx];
return 0;
}
static int rt9455_charger_get_voltage(struct rt9455_info *info,
union power_supply_propval *val)
{
int voltage;
int ret;
ret = rt9455_get_field_val(info, F_VOREG,
rt9455_voreg_values,
ARRAY_SIZE(rt9455_voreg_values),
&voltage);
if (ret) {
dev_err(&info->client->dev, "Failed to read VOREG value\n");
return ret;
}
val->intval = voltage;
return 0;
}
static int rt9455_charger_get_voltage_max(struct rt9455_info *info,
union power_supply_propval *val)
{
int idx = ARRAY_SIZE(rt9455_vmreg_values) - 1;
val->intval = rt9455_vmreg_values[idx];
return 0;
}
static int rt9455_charger_get_term_current(struct rt9455_info *info,
union power_supply_propval *val)
{
struct device *dev = &info->client->dev;
int ichrg, ieoc_percentage, ret;
ret = rt9455_get_field_val(info, F_ICHRG,
rt9455_ichrg_values,
ARRAY_SIZE(rt9455_ichrg_values),
&ichrg);
if (ret) {
dev_err(dev, "Failed to read ICHRG value\n");
return ret;
}
ret = rt9455_get_field_val(info, F_IEOC_PERCENTAGE,
rt9455_ieoc_percentage_values,
ARRAY_SIZE(rt9455_ieoc_percentage_values),
&ieoc_percentage);
if (ret) {
dev_err(dev, "Failed to read IEOC value\n");
return ret;
}
val->intval = ichrg * ieoc_percentage / 100;
return 0;
}
static int rt9455_charger_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct rt9455_info *info = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
return rt9455_charger_get_status(info, val);
case POWER_SUPPLY_PROP_HEALTH:
return rt9455_charger_get_health(info, val);
case POWER_SUPPLY_PROP_PRESENT:
return rt9455_charger_get_battery_presence(info, val);
case POWER_SUPPLY_PROP_ONLINE:
return rt9455_charger_get_online(info, val);
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
return rt9455_charger_get_current(info, val);
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
return rt9455_charger_get_current_max(info, val);
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
return rt9455_charger_get_voltage(info, val);
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
return rt9455_charger_get_voltage_max(info, val);
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
return 0;
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
return rt9455_charger_get_term_current(info, val);
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = RT9455_MODEL_NAME;
return 0;
case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = RT9455_MANUFACTURER;
return 0;
default:
return -ENODATA;
}
}
static int rt9455_hw_init(struct rt9455_info *info, u32 ichrg,
u32 ieoc_percentage,
u32 mivr, u32 iaicr)
{
struct device *dev = &info->client->dev;
int idx, ret;
ret = rt9455_register_reset(info);
if (ret) {
dev_err(dev, "Power On Reset failed\n");
return ret;
}
/* Set TE bit in order to enable end of charge detection */
ret = regmap_field_write(info->regmap_fields[F_TE], 1);
if (ret) {
dev_err(dev, "Failed to set TE bit\n");
return ret;
}
/* Set TE_SHDN_EN bit in order to enable end of charge detection */
ret = regmap_field_write(info->regmap_fields[F_TE_SHDN_EN], 1);
if (ret) {
dev_err(dev, "Failed to set TE_SHDN_EN bit\n");
return ret;
}
/*
* Set BATD_EN bit in order to enable battery detection
* when charging is done
*/
ret = regmap_field_write(info->regmap_fields[F_BATD_EN], 1);
if (ret) {
dev_err(dev, "Failed to set BATD_EN bit\n");
return ret;
}
/*
* Disable Safety Timer. In charge mode, this timer terminates charging
* if no read or write via I2C is done within 32 minutes. This timer
* avoids overcharging the baterry when the OS is not loaded and the
* charger is connected to a power source.
* In boost mode, this timer triggers BST32SI interrupt if no read or
* write via I2C is done within 32 seconds.
* When the OS is loaded and the charger driver is inserted, it is used
* delayed_work, named max_charging_time_work, to avoid overcharging
* the battery.
*/
ret = regmap_field_write(info->regmap_fields[F_TMR_EN], 0x00);
if (ret) {
dev_err(dev, "Failed to disable Safety Timer\n");
return ret;
}
/* Set ICHRG to value retrieved from device-specific data */
ret = rt9455_set_field_val(info, F_ICHRG,
rt9455_ichrg_values,
ARRAY_SIZE(rt9455_ichrg_values), ichrg);
if (ret) {
dev_err(dev, "Failed to set ICHRG value\n");
return ret;
}
/* Set IEOC Percentage to value retrieved from device-specific data */
ret = rt9455_set_field_val(info, F_IEOC_PERCENTAGE,
rt9455_ieoc_percentage_values,
ARRAY_SIZE(rt9455_ieoc_percentage_values),
ieoc_percentage);
if (ret) {
dev_err(dev, "Failed to set IEOC Percentage value\n");
return ret;
}
/* Set VOREG to value retrieved from device-specific data */
ret = rt9455_set_field_val(info, F_VOREG,
rt9455_voreg_values,
ARRAY_SIZE(rt9455_voreg_values),
info->voreg);
if (ret) {
dev_err(dev, "Failed to set VOREG value\n");
return ret;
}
/* Set VMREG value to maximum (4.45V). */
idx = ARRAY_SIZE(rt9455_vmreg_values) - 1;
ret = rt9455_set_field_val(info, F_VMREG,
rt9455_vmreg_values,
ARRAY_SIZE(rt9455_vmreg_values),
rt9455_vmreg_values[idx]);
if (ret) {
dev_err(dev, "Failed to set VMREG value\n");
return ret;
}
/*
* Set MIVR to value retrieved from device-specific data.
* If no value is specified, default value for MIVR is 4.5V.
*/
if (mivr == -1)
mivr = 4500000;
ret = rt9455_set_field_val(info, F_MIVR,
rt9455_mivr_values,
ARRAY_SIZE(rt9455_mivr_values), mivr);
if (ret) {
dev_err(dev, "Failed to set MIVR value\n");
return ret;
}
/*
* Set IAICR to value retrieved from device-specific data.
* If no value is specified, default value for IAICR is 500 mA.
*/
if (iaicr == -1)
iaicr = 500000;
ret = rt9455_set_field_val(info, F_IAICR,
rt9455_iaicr_values,
ARRAY_SIZE(rt9455_iaicr_values), iaicr);
if (ret) {
dev_err(dev, "Failed to set IAICR value\n");
return ret;
}
/*
* Set IAICR_INT bit so that IAICR value is determined by IAICR bits
* and not by OTG pin.
*/
ret = regmap_field_write(info->regmap_fields[F_IAICR_INT], 0x01);
if (ret) {
dev_err(dev, "Failed to set IAICR_INT bit\n");
return ret;
}
/*
* Disable CHMIVRI interrupt. Because the driver sets MIVR value,
* CHMIVRI is triggered, but there is no action to be taken by the
* driver when CHMIVRI is triggered.
*/
ret = regmap_field_write(info->regmap_fields[F_CHMIVRIM], 0x01);
if (ret) {
dev_err(dev, "Failed to mask CHMIVRI interrupt\n");
return ret;
}
return 0;
}
#if IS_ENABLED(CONFIG_USB_PHY)
/*
* Before setting the charger into boost mode, boost output voltage is
* set. This is needed because boost output voltage may differ from battery
* regulation voltage. F_VOREG bits represent either battery regulation voltage
* or boost output voltage, depending on the mode the charger is. Both battery
* regulation voltage and boost output voltage are read from DT/ACPI during
* probe.
*/
static int rt9455_set_boost_voltage_before_boost_mode(struct rt9455_info *info)
{
struct device *dev = &info->client->dev;
int ret;
ret = rt9455_set_field_val(info, F_VOREG,
rt9455_boost_voltage_values,
ARRAY_SIZE(rt9455_boost_voltage_values),
info->boost_voltage);
if (ret) {
dev_err(dev, "Failed to set boost output voltage value\n");
return ret;
}
return 0;
}
#endif
/*
* Before setting the charger into charge mode, battery regulation voltage is
* set. This is needed because boost output voltage may differ from battery
* regulation voltage. F_VOREG bits represent either battery regulation voltage
* or boost output voltage, depending on the mode the charger is. Both battery
* regulation voltage and boost output voltage are read from DT/ACPI during
* probe.
*/
static int rt9455_set_voreg_before_charge_mode(struct rt9455_info *info)
{
struct device *dev = &info->client->dev;
int ret;
ret = rt9455_set_field_val(info, F_VOREG,
rt9455_voreg_values,
ARRAY_SIZE(rt9455_voreg_values),
info->voreg);
if (ret) {
dev_err(dev, "Failed to set VOREG value\n");
return ret;
}
return 0;
}
static int rt9455_irq_handler_check_irq1_register(struct rt9455_info *info,
bool *_is_battery_absent,
bool *_alert_userspace)
{
unsigned int irq1, mask1, mask2;
struct device *dev = &info->client->dev;
bool is_battery_absent = false;
bool alert_userspace = false;
int ret;
ret = regmap_read(info->regmap, RT9455_REG_IRQ1, &irq1);
if (ret) {
dev_err(dev, "Failed to read IRQ1 register\n");
return ret;
}
ret = regmap_read(info->regmap, RT9455_REG_MASK1, &mask1);
if (ret) {
dev_err(dev, "Failed to read MASK1 register\n");
return ret;
}
if (irq1 & GET_MASK(F_TSDI)) {
dev_err(dev, "Thermal shutdown fault occurred\n");
alert_userspace = true;
}
if (irq1 & GET_MASK(F_VINOVPI)) {
dev_err(dev, "Overvoltage input occurred\n");
alert_userspace = true;
}
if (irq1 & GET_MASK(F_BATAB)) {
dev_err(dev, "Battery absence occurred\n");
is_battery_absent = true;
alert_userspace = true;
if ((mask1 & GET_MASK(F_BATABM)) == 0) {
ret = regmap_field_write(info->regmap_fields[F_BATABM],
0x01);
if (ret) {
dev_err(dev, "Failed to mask BATAB interrupt\n");
return ret;
}
}
ret = regmap_read(info->regmap, RT9455_REG_MASK2, &mask2);
if (ret) {
dev_err(dev, "Failed to read MASK2 register\n");
return ret;
}
if (mask2 & GET_MASK(F_CHTERMIM)) {
ret = regmap_field_write(
info->regmap_fields[F_CHTERMIM], 0x00);
if (ret) {
dev_err(dev, "Failed to unmask CHTERMI interrupt\n");
return ret;
}
}
if (mask2 & GET_MASK(F_CHRCHGIM)) {
ret = regmap_field_write(
info->regmap_fields[F_CHRCHGIM], 0x00);
if (ret) {
dev_err(dev, "Failed to unmask CHRCHGI interrupt\n");
return ret;
}
}
/*
* When the battery is absent, max_charging_time_work is
* cancelled, since no charging is done.
*/
cancel_delayed_work_sync(&info->max_charging_time_work);
/*
* Since no interrupt is triggered when the battery is
* reconnected, max_charging_time_work is not rescheduled.
* Therefore, batt_presence_work is scheduled to check whether
* the battery is still absent or not.
*/
queue_delayed_work(system_power_efficient_wq,
&info->batt_presence_work,
RT9455_BATT_PRESENCE_DELAY * HZ);
}
*_is_battery_absent = is_battery_absent;
if (alert_userspace)
*_alert_userspace = alert_userspace;
return 0;
}
static int rt9455_irq_handler_check_irq2_register(struct rt9455_info *info,
bool is_battery_absent,
bool *_alert_userspace)
{
unsigned int irq2, mask2;
struct device *dev = &info->client->dev;
bool alert_userspace = false;
int ret;
ret = regmap_read(info->regmap, RT9455_REG_IRQ2, &irq2);
if (ret) {
dev_err(dev, "Failed to read IRQ2 register\n");
return ret;
}
ret = regmap_read(info->regmap, RT9455_REG_MASK2, &mask2);
if (ret) {
dev_err(dev, "Failed to read MASK2 register\n");
return ret;
}
if (irq2 & GET_MASK(F_CHRVPI)) {
dev_dbg(dev, "Charger fault occurred\n");
alert_userspace = true;
/*
* CHRVPI bit is set in 2 cases:
* 1. when the power source is connected to the charger.
* 2. when the power source is disconnected from the charger.
* To identify the case, PWR_RDY bit is checked. Because
* PWR_RDY bit is set / cleared after CHRVPI interrupt is
* triggered, it is used delayed_work to later read PWR_RDY bit.
*/
queue_delayed_work(system_power_efficient_wq,
&info->pwr_rdy_work,
RT9455_PWR_RDY_DELAY * HZ);
}
if (irq2 & GET_MASK(F_CHBATOVI)) {
dev_err(dev, "Battery OVP occurred\n");
alert_userspace = true;
}
if (irq2 & GET_MASK(F_CHTERMI)) {
dev_dbg(dev, "Charge terminated\n");
if (!is_battery_absent) {
if ((mask2 & GET_MASK(F_CHTERMIM)) == 0) {
ret = regmap_field_write(
info->regmap_fields[F_CHTERMIM], 0x01);
if (ret) {
dev_err(dev, "Failed to mask CHTERMI interrupt\n");
return ret;
}
/*
* Update MASK2 value, since CHTERMIM bit is
* set.
*/
mask2 = mask2 | GET_MASK(F_CHTERMIM);
}
cancel_delayed_work_sync(&info->max_charging_time_work);
alert_userspace = true;
}
}
if (irq2 & GET_MASK(F_CHRCHGI)) {
dev_dbg(dev, "Recharge request\n");
ret = regmap_field_write(info->regmap_fields[F_CHG_EN],
RT9455_CHARGE_ENABLE);
if (ret) {
dev_err(dev, "Failed to enable charging\n");
return ret;
}
if (mask2 & GET_MASK(F_CHTERMIM)) {
ret = regmap_field_write(
info->regmap_fields[F_CHTERMIM], 0x00);
if (ret) {
dev_err(dev, "Failed to unmask CHTERMI interrupt\n");
return ret;
}
/* Update MASK2 value, since CHTERMIM bit is cleared. */
mask2 = mask2 & ~GET_MASK(F_CHTERMIM);
}
if (!is_battery_absent) {
/*
* No need to check whether the charger is connected to
* power source when CHRCHGI is received, since CHRCHGI
* is not triggered if the charger is not connected to
* the power source.
*/
queue_delayed_work(system_power_efficient_wq,
&info->max_charging_time_work,
RT9455_MAX_CHARGING_TIME * HZ);
alert_userspace = true;
}
}
if (irq2 & GET_MASK(F_CH32MI)) {
dev_err(dev, "Charger fault. 32 mins timeout occurred\n");
alert_userspace = true;
}
if (irq2 & GET_MASK(F_CHTREGI)) {
dev_warn(dev,
"Charger warning. Thermal regulation loop active\n");
alert_userspace = true;
}
if (irq2 & GET_MASK(F_CHMIVRI)) {
dev_dbg(dev,
"Charger warning. Input voltage MIVR loop active\n");
}
if (alert_userspace)
*_alert_userspace = alert_userspace;
return 0;
}
static int rt9455_irq_handler_check_irq3_register(struct rt9455_info *info,
bool *_alert_userspace)
{
unsigned int irq3, mask3;
struct device *dev = &info->client->dev;
bool alert_userspace = false;
int ret;
ret = regmap_read(info->regmap, RT9455_REG_IRQ3, &irq3);
if (ret) {
dev_err(dev, "Failed to read IRQ3 register\n");
return ret;
}
ret = regmap_read(info->regmap, RT9455_REG_MASK3, &mask3);
if (ret) {
dev_err(dev, "Failed to read MASK3 register\n");
return ret;
}
if (irq3 & GET_MASK(F_BSTBUSOVI)) {
dev_err(dev, "Boost fault. Overvoltage input occurred\n");
alert_userspace = true;
}
if (irq3 & GET_MASK(F_BSTOLI)) {
dev_err(dev, "Boost fault. Overload\n");
alert_userspace = true;
}
if (irq3 & GET_MASK(F_BSTLOWVI)) {
dev_err(dev, "Boost fault. Battery voltage too low\n");
alert_userspace = true;
}
if (irq3 & GET_MASK(F_BST32SI)) {
dev_err(dev, "Boost fault. 32 seconds timeout occurred.\n");
alert_userspace = true;
}
if (alert_userspace) {
dev_info(dev, "Boost fault occurred, therefore the charger goes into charge mode\n");
ret = rt9455_set_voreg_before_charge_mode(info);
if (ret) {
dev_err(dev, "Failed to set VOREG before entering charge mode\n");
return ret;
}
ret = regmap_field_write(info->regmap_fields[F_OPA_MODE],
RT9455_CHARGE_MODE);
if (ret) {
dev_err(dev, "Failed to set charger in charge mode\n");
return ret;
}
*_alert_userspace = alert_userspace;
}
return 0;
}
static irqreturn_t rt9455_irq_handler_thread(int irq, void *data)
{
struct rt9455_info *info = data;
struct device *dev;
bool alert_userspace = false;
bool is_battery_absent = false;
unsigned int status;
int ret;
if (!info)
return IRQ_NONE;
dev = &info->client->dev;
if (irq != info->client->irq) {
dev_err(dev, "Interrupt is not for RT9455 charger\n");
return IRQ_NONE;
}
ret = regmap_field_read(info->regmap_fields[F_STAT], &status);
if (ret) {
dev_err(dev, "Failed to read STAT bits\n");
return IRQ_HANDLED;
}
dev_dbg(dev, "Charger status is %d\n", status);
/*
* Each function that processes an IRQ register receives as output
* parameter alert_userspace pointer. alert_userspace is set to true
* in such a function only if an interrupt has occurred in the
* respective interrupt register. This way, it is avoided the following
* case: interrupt occurs only in IRQ1 register,
* rt9455_irq_handler_check_irq1_register() function sets to true
* alert_userspace, but rt9455_irq_handler_check_irq2_register()
* and rt9455_irq_handler_check_irq3_register() functions set to false
* alert_userspace and power_supply_changed() is never called.
*/
ret = rt9455_irq_handler_check_irq1_register(info, &is_battery_absent,
&alert_userspace);
if (ret) {
dev_err(dev, "Failed to handle IRQ1 register\n");
return IRQ_HANDLED;
}
ret = rt9455_irq_handler_check_irq2_register(info, is_battery_absent,
&alert_userspace);
if (ret) {
dev_err(dev, "Failed to handle IRQ2 register\n");
return IRQ_HANDLED;
}
ret = rt9455_irq_handler_check_irq3_register(info, &alert_userspace);
if (ret) {
dev_err(dev, "Failed to handle IRQ3 register\n");
return IRQ_HANDLED;
}
if (alert_userspace) {
/*
* Sometimes, an interrupt occurs while rt9455_probe() function
* is executing and power_supply_register() is not yet called.
* Do not call power_supply_charged() in this case.
*/
if (info->charger)
power_supply_changed(info->charger);
}
return IRQ_HANDLED;
}
static int rt9455_discover_charger(struct rt9455_info *info, u32 *ichrg,
u32 *ieoc_percentage,
u32 *mivr, u32 *iaicr)
{
struct device *dev = &info->client->dev;
int ret;
if (!dev->of_node && !ACPI_HANDLE(dev)) {
dev_err(dev, "No support for either device tree or ACPI\n");
return -EINVAL;
}
/*
* ICHRG, IEOC_PERCENTAGE, VOREG and boost output voltage are mandatory
* parameters.
*/
ret = device_property_read_u32(dev, "richtek,output-charge-current",
ichrg);
if (ret) {
dev_err(dev, "Error: missing \"output-charge-current\" property\n");
return ret;
}
ret = device_property_read_u32(dev, "richtek,end-of-charge-percentage",
ieoc_percentage);
if (ret) {
dev_err(dev, "Error: missing \"end-of-charge-percentage\" property\n");
return ret;
}
ret = device_property_read_u32(dev,
"richtek,battery-regulation-voltage",
&info->voreg);
if (ret) {
dev_err(dev, "Error: missing \"battery-regulation-voltage\" property\n");
return ret;
}
ret = device_property_read_u32(dev, "richtek,boost-output-voltage",
&info->boost_voltage);
if (ret) {
dev_err(dev, "Error: missing \"boost-output-voltage\" property\n");
return ret;
}
/*
* MIVR and IAICR are optional parameters. Do not return error if one of
* them is not present in ACPI table or device tree specification.
*/
device_property_read_u32(dev, "richtek,min-input-voltage-regulation",
mivr);
device_property_read_u32(dev, "richtek,avg-input-current-regulation",
iaicr);
return 0;
}
#if IS_ENABLED(CONFIG_USB_PHY)
static int rt9455_usb_event_none(struct rt9455_info *info,
u8 opa_mode, u8 iaicr)
{
struct device *dev = &info->client->dev;
int ret;
if (opa_mode == RT9455_BOOST_MODE) {
ret = rt9455_set_voreg_before_charge_mode(info);
if (ret) {
dev_err(dev, "Failed to set VOREG before entering charge mode\n");
return ret;
}
/*
* If the charger is in boost mode, and it has received
* USB_EVENT_NONE, this means the consumer device powered by the
* charger is not connected anymore.
* In this case, the charger goes into charge mode.
*/
dev_dbg(dev, "USB_EVENT_NONE received, therefore the charger goes into charge mode\n");
ret = regmap_field_write(info->regmap_fields[F_OPA_MODE],
RT9455_CHARGE_MODE);
if (ret) {
dev_err(dev, "Failed to set charger in charge mode\n");
return NOTIFY_DONE;
}
}
dev_dbg(dev, "USB_EVENT_NONE received, therefore IAICR is set to its minimum value\n");
if (iaicr != RT9455_IAICR_100MA) {
ret = regmap_field_write(info->regmap_fields[F_IAICR],
RT9455_IAICR_100MA);
if (ret) {
dev_err(dev, "Failed to set IAICR value\n");
return NOTIFY_DONE;
}
}
return NOTIFY_OK;
}
static int rt9455_usb_event_vbus(struct rt9455_info *info,
u8 opa_mode, u8 iaicr)
{
struct device *dev = &info->client->dev;
int ret;
if (opa_mode == RT9455_BOOST_MODE) {
ret = rt9455_set_voreg_before_charge_mode(info);
if (ret) {
dev_err(dev, "Failed to set VOREG before entering charge mode\n");
return ret;
}
/*
* If the charger is in boost mode, and it has received
* USB_EVENT_VBUS, this means the consumer device powered by the
* charger is not connected anymore.
* In this case, the charger goes into charge mode.
*/
dev_dbg(dev, "USB_EVENT_VBUS received, therefore the charger goes into charge mode\n");
ret = regmap_field_write(info->regmap_fields[F_OPA_MODE],
RT9455_CHARGE_MODE);
if (ret) {
dev_err(dev, "Failed to set charger in charge mode\n");
return NOTIFY_DONE;
}
}
dev_dbg(dev, "USB_EVENT_VBUS received, therefore IAICR is set to 500 mA\n");
if (iaicr != RT9455_IAICR_500MA) {
ret = regmap_field_write(info->regmap_fields[F_IAICR],
RT9455_IAICR_500MA);
if (ret) {
dev_err(dev, "Failed to set IAICR value\n");
return NOTIFY_DONE;
}
}
return NOTIFY_OK;
}
static int rt9455_usb_event_id(struct rt9455_info *info,
u8 opa_mode, u8 iaicr)
{
struct device *dev = &info->client->dev;
int ret;
if (opa_mode == RT9455_CHARGE_MODE) {
ret = rt9455_set_boost_voltage_before_boost_mode(info);
if (ret) {
dev_err(dev, "Failed to set boost output voltage before entering boost mode\n");
return ret;
}
/*
* If the charger is in charge mode, and it has received
* USB_EVENT_ID, this means a consumer device is connected and
* it should be powered by the charger.
* In this case, the charger goes into boost mode.
*/
dev_dbg(dev, "USB_EVENT_ID received, therefore the charger goes into boost mode\n");
ret = regmap_field_write(info->regmap_fields[F_OPA_MODE],
RT9455_BOOST_MODE);
if (ret) {
dev_err(dev, "Failed to set charger in boost mode\n");
return NOTIFY_DONE;
}
}
dev_dbg(dev, "USB_EVENT_ID received, therefore IAICR is set to its minimum value\n");
if (iaicr != RT9455_IAICR_100MA) {
ret = regmap_field_write(info->regmap_fields[F_IAICR],
RT9455_IAICR_100MA);
if (ret) {
dev_err(dev, "Failed to set IAICR value\n");
return NOTIFY_DONE;
}
}
return NOTIFY_OK;
}
static int rt9455_usb_event_charger(struct rt9455_info *info,
u8 opa_mode, u8 iaicr)
{
struct device *dev = &info->client->dev;
int ret;
if (opa_mode == RT9455_BOOST_MODE) {
ret = rt9455_set_voreg_before_charge_mode(info);
if (ret) {
dev_err(dev, "Failed to set VOREG before entering charge mode\n");
return ret;
}
/*
* If the charger is in boost mode, and it has received
* USB_EVENT_CHARGER, this means the consumer device powered by
* the charger is not connected anymore.
* In this case, the charger goes into charge mode.
*/
dev_dbg(dev, "USB_EVENT_CHARGER received, therefore the charger goes into charge mode\n");
ret = regmap_field_write(info->regmap_fields[F_OPA_MODE],
RT9455_CHARGE_MODE);
if (ret) {
dev_err(dev, "Failed to set charger in charge mode\n");
return NOTIFY_DONE;
}
}
dev_dbg(dev, "USB_EVENT_CHARGER received, therefore IAICR is set to no current limit\n");
if (iaicr != RT9455_IAICR_NO_LIMIT) {
ret = regmap_field_write(info->regmap_fields[F_IAICR],
RT9455_IAICR_NO_LIMIT);
if (ret) {
dev_err(dev, "Failed to set IAICR value\n");
return NOTIFY_DONE;
}
}
return NOTIFY_OK;
}
static int rt9455_usb_event(struct notifier_block *nb,
unsigned long event, void *power)
{
struct rt9455_info *info = container_of(nb, struct rt9455_info, nb);
struct device *dev = &info->client->dev;
unsigned int opa_mode, iaicr;
int ret;
/*
* Determine whether the charger is in charge mode
* or in boost mode.
*/
ret = regmap_field_read(info->regmap_fields[F_OPA_MODE],
&opa_mode);
if (ret) {
dev_err(dev, "Failed to read OPA_MODE value\n");
return NOTIFY_DONE;
}
ret = regmap_field_read(info->regmap_fields[F_IAICR],
&iaicr);
if (ret) {
dev_err(dev, "Failed to read IAICR value\n");
return NOTIFY_DONE;
}
dev_dbg(dev, "Received USB event %lu\n", event);
switch (event) {
case USB_EVENT_NONE:
return rt9455_usb_event_none(info, opa_mode, iaicr);
case USB_EVENT_VBUS:
return rt9455_usb_event_vbus(info, opa_mode, iaicr);
case USB_EVENT_ID:
return rt9455_usb_event_id(info, opa_mode, iaicr);
case USB_EVENT_CHARGER:
return rt9455_usb_event_charger(info, opa_mode, iaicr);
default:
dev_err(dev, "Unknown USB event\n");
}
return NOTIFY_DONE;
}
#endif
static void rt9455_pwr_rdy_work_callback(struct work_struct *work)
{
struct rt9455_info *info = container_of(work, struct rt9455_info,
pwr_rdy_work.work);
struct device *dev = &info->client->dev;
unsigned int pwr_rdy;
int ret;
ret = regmap_field_read(info->regmap_fields[F_PWR_RDY], &pwr_rdy);
if (ret) {
dev_err(dev, "Failed to read PWR_RDY bit\n");
return;
}
switch (pwr_rdy) {
case RT9455_PWR_FAULT:
dev_dbg(dev, "Charger disconnected from power source\n");
cancel_delayed_work_sync(&info->max_charging_time_work);
break;
case RT9455_PWR_GOOD:
dev_dbg(dev, "Charger connected to power source\n");
ret = regmap_field_write(info->regmap_fields[F_CHG_EN],
RT9455_CHARGE_ENABLE);
if (ret) {
dev_err(dev, "Failed to enable charging\n");
return;
}
queue_delayed_work(system_power_efficient_wq,
&info->max_charging_time_work,
RT9455_MAX_CHARGING_TIME * HZ);
break;
}
}
static void rt9455_max_charging_time_work_callback(struct work_struct *work)
{
struct rt9455_info *info = container_of(work, struct rt9455_info,
max_charging_time_work.work);
struct device *dev = &info->client->dev;
int ret;
dev_err(dev, "Battery has been charging for at least 6 hours and is not yet fully charged. Battery is dead, therefore charging is disabled.\n");
ret = regmap_field_write(info->regmap_fields[F_CHG_EN],
RT9455_CHARGE_DISABLE);
if (ret)
dev_err(dev, "Failed to disable charging\n");
}
static void rt9455_batt_presence_work_callback(struct work_struct *work)
{
struct rt9455_info *info = container_of(work, struct rt9455_info,
batt_presence_work.work);
struct device *dev = &info->client->dev;
unsigned int irq1, mask1;
int ret;
ret = regmap_read(info->regmap, RT9455_REG_IRQ1, &irq1);
if (ret) {
dev_err(dev, "Failed to read IRQ1 register\n");
return;
}
/*
* If the battery is still absent, batt_presence_work is rescheduled.
* Otherwise, max_charging_time is scheduled.
*/
if (irq1 & GET_MASK(F_BATAB)) {
queue_delayed_work(system_power_efficient_wq,
&info->batt_presence_work,
RT9455_BATT_PRESENCE_DELAY * HZ);
} else {
queue_delayed_work(system_power_efficient_wq,
&info->max_charging_time_work,
RT9455_MAX_CHARGING_TIME * HZ);
ret = regmap_read(info->regmap, RT9455_REG_MASK1, &mask1);
if (ret) {
dev_err(dev, "Failed to read MASK1 register\n");
return;
}
if (mask1 & GET_MASK(F_BATABM)) {
ret = regmap_field_write(info->regmap_fields[F_BATABM],
0x00);
if (ret)
dev_err(dev, "Failed to unmask BATAB interrupt\n");
}
}
}
static const struct power_supply_desc rt9455_charger_desc = {
.name = RT9455_DRIVER_NAME,
.type = POWER_SUPPLY_TYPE_USB,
.properties = rt9455_charger_properties,
.num_properties = ARRAY_SIZE(rt9455_charger_properties),
.get_property = rt9455_charger_get_property,
};
static bool rt9455_is_writeable_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case RT9455_REG_DEV_ID:
case RT9455_REG_IRQ1:
case RT9455_REG_IRQ2:
case RT9455_REG_IRQ3:
return false;
default:
return true;
}
}
static bool rt9455_is_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case RT9455_REG_DEV_ID:
case RT9455_REG_CTRL5:
case RT9455_REG_CTRL6:
return false;
default:
return true;
}
}
static const struct regmap_config rt9455_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.writeable_reg = rt9455_is_writeable_reg,
.volatile_reg = rt9455_is_volatile_reg,
.max_register = RT9455_REG_MASK3,
.cache_type = REGCACHE_RBTREE,
};
static int rt9455_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 rt9455_info *info;
struct power_supply_config rt9455_charger_config = {};
/*
* Mandatory device-specific data values. Also, VOREG and boost output
* voltage are mandatory values, but they are stored in rt9455_info
* structure.
*/
u32 ichrg, ieoc_percentage;
/* Optional device-specific data values. */
u32 mivr = -1, iaicr = -1;
int i, ret;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(dev, "No support for SMBUS_BYTE_DATA\n");
return -ENODEV;
}
info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
info->client = client;
i2c_set_clientdata(client, info);
info->regmap = devm_regmap_init_i2c(client,
&rt9455_regmap_config);
if (IS_ERR(info->regmap)) {
dev_err(dev, "Failed to initialize register map\n");
return -EINVAL;
}
for (i = 0; i < F_MAX_FIELDS; i++) {
info->regmap_fields[i] =
devm_regmap_field_alloc(dev, info->regmap,
rt9455_reg_fields[i]);
if (IS_ERR(info->regmap_fields[i])) {
dev_err(dev,
"Failed to allocate regmap field = %d\n", i);
return PTR_ERR(info->regmap_fields[i]);
}
}
ret = rt9455_discover_charger(info, &ichrg, &ieoc_percentage,
&mivr, &iaicr);
if (ret) {
dev_err(dev, "Failed to discover charger\n");
return ret;
}
#if IS_ENABLED(CONFIG_USB_PHY)
info->usb_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2);
if (IS_ERR(info->usb_phy)) {
dev_err(dev, "Failed to get USB transceiver\n");
} else {
info->nb.notifier_call = rt9455_usb_event;
ret = usb_register_notifier(info->usb_phy, &info->nb);
if (ret) {
dev_err(dev, "Failed to register USB notifier\n");
/*
* If usb_register_notifier() fails, set notifier_call
* to NULL, to avoid calling usb_unregister_notifier().
*/
info->nb.notifier_call = NULL;
}
}
#endif
INIT_DEFERRABLE_WORK(&info->pwr_rdy_work, rt9455_pwr_rdy_work_callback);
INIT_DEFERRABLE_WORK(&info->max_charging_time_work,
rt9455_max_charging_time_work_callback);
INIT_DEFERRABLE_WORK(&info->batt_presence_work,
rt9455_batt_presence_work_callback);
rt9455_charger_config.of_node = dev->of_node;
rt9455_charger_config.drv_data = info;
rt9455_charger_config.supplied_to = rt9455_charger_supplied_to;
rt9455_charger_config.num_supplicants =
ARRAY_SIZE(rt9455_charger_supplied_to);
ret = devm_request_threaded_irq(dev, client->irq, NULL,
rt9455_irq_handler_thread,
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
RT9455_DRIVER_NAME, info);
if (ret) {
dev_err(dev, "Failed to register IRQ handler\n");
goto put_usb_notifier;
}
ret = rt9455_hw_init(info, ichrg, ieoc_percentage, mivr, iaicr);
if (ret) {
dev_err(dev, "Failed to set charger to its default values\n");
goto put_usb_notifier;
}
info->charger = devm_power_supply_register(dev, &rt9455_charger_desc,
&rt9455_charger_config);
if (IS_ERR(info->charger)) {
dev_err(dev, "Failed to register charger\n");
ret = PTR_ERR(info->charger);
goto put_usb_notifier;
}
return 0;
put_usb_notifier:
#if IS_ENABLED(CONFIG_USB_PHY)
if (info->nb.notifier_call) {
usb_unregister_notifier(info->usb_phy, &info->nb);
info->nb.notifier_call = NULL;
}
#endif
return ret;
}
static int rt9455_remove(struct i2c_client *client)
{
int ret;
struct rt9455_info *info = i2c_get_clientdata(client);
ret = rt9455_register_reset(info);
if (ret)
dev_err(&info->client->dev, "Failed to set charger to its default values\n");
#if IS_ENABLED(CONFIG_USB_PHY)
if (info->nb.notifier_call)
usb_unregister_notifier(info->usb_phy, &info->nb);
#endif
cancel_delayed_work_sync(&info->pwr_rdy_work);
cancel_delayed_work_sync(&info->max_charging_time_work);
cancel_delayed_work_sync(&info->batt_presence_work);
return ret;
}
static const struct i2c_device_id rt9455_i2c_id_table[] = {
{ RT9455_DRIVER_NAME, 0 },
{ },
};
MODULE_DEVICE_TABLE(i2c, rt9455_i2c_id_table);
static const struct of_device_id rt9455_of_match[] = {
{ .compatible = "richtek,rt9455", },
{ },
};
MODULE_DEVICE_TABLE(of, rt9455_of_match);
static const struct acpi_device_id rt9455_i2c_acpi_match[] = {
{ "RT945500", 0 },
{ }
};
MODULE_DEVICE_TABLE(acpi, rt9455_i2c_acpi_match);
static struct i2c_driver rt9455_driver = {
.probe = rt9455_probe,
.remove = rt9455_remove,
.id_table = rt9455_i2c_id_table,
.driver = {
.name = RT9455_DRIVER_NAME,
.of_match_table = of_match_ptr(rt9455_of_match),
.acpi_match_table = ACPI_PTR(rt9455_i2c_acpi_match),
},
};
module_i2c_driver(rt9455_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anda-Maria Nicolae <anda-maria.nicolae@intel.com>");
MODULE_ALIAS("i2c:rt9455-charger");
MODULE_DESCRIPTION("Richtek RT9455 Charger Driver");
......@@ -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,15 +887,18 @@ 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.
*/
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,
&psy_cfg);
......@@ -991,3 +996,7 @@ module_i2c_driver(sbs_battery_driver);
MODULE_DESCRIPTION("SBS battery monitor driver");
MODULE_LICENSE("GPL");
module_param(force_load, bool, S_IRUSR | S_IRGRP | S_IROTH);
MODULE_PARM_DESC(force_load,
"Attempt to load the driver even if no battery is connected");
......@@ -609,6 +609,7 @@ static int wm831x_power_probe(struct platform_device *pdev)
return ret;
err_bat_irq:
--i;
for (; i >= 0; i--) {
irq = platform_get_irq_byname(pdev, wm831x_bat_irqs[i]);
free_irq(irq, power);
......
......@@ -275,4 +275,11 @@ struct axp20x_fg_pdata {
int thermistor_curve[MAX_THERM_CURVE_SIZE][2];
};
struct axp20x_chrg_pdata {
int max_cc;
int max_cv;
int def_cc;
int def_cv;
};
#endif /* __LINUX_MFD_AXP20X_H */
......@@ -215,6 +215,10 @@ struct max17042_platform_data {
* the datasheet although it can be changed by board designers.
*/
unsigned int r_sns;
int vmin; /* in millivolts */
int vmax; /* in millivolts */
int temp_min; /* in tenths of degree Celsius */
int temp_max; /* in tenths of degree Celsius */
};
#endif /* __MAX17042_BATTERY_H_ */
......@@ -206,6 +206,11 @@ struct power_supply_desc {
int (*set_property)(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val);
/*
* property_is_writeable() will be called during registration
* of power supply. If this happens during device probe then it must
* not access internal data of device (because probe did not end).
*/
int (*property_is_writeable)(struct power_supply *psy,
enum power_supply_property psp);
void (*external_power_changed)(struct power_supply *psy);
......@@ -237,6 +242,7 @@ struct power_supply {
/* private */
struct device dev;
struct work_struct changed_work;
struct delayed_work deferred_register_work;
spinlock_t changed_lock;
bool changed;
atomic_t use_cnt;
......@@ -286,10 +292,15 @@ extern void power_supply_put(struct power_supply *psy);
#ifdef CONFIG_OF
extern struct power_supply *power_supply_get_by_phandle(struct device_node *np,
const char *property);
extern struct power_supply *devm_power_supply_get_by_phandle(
struct device *dev, const char *property);
#else /* !CONFIG_OF */
static inline struct power_supply *
power_supply_get_by_phandle(struct device_node *np, const char *property)
{ return NULL; }
static inline struct power_supply *
devm_power_supply_get_by_phandle(struct device *dev, const char *property)
{ return NULL; }
#endif /* CONFIG_OF */
extern void power_supply_changed(struct power_supply *psy);
extern int power_supply_am_i_supplied(struct power_supply *psy);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment