提交 a22a0fdb 编写于 作者: L Linus Torvalds

Merge tag 'for-v3.12' of git://git.infradead.org/battery-2.6

Pull battery/power supply driver updates from Anton Vorontsov:
 "New drivers:

   - APM X-Gene system reboot driver by Feng Kan and Loc Ho (APM).

   - Qualcomm MSM reboot/poweroff driver by Abhimanyu Kapur (Codeaurora).

   - Texas Instruments BQ24190 charger driver by Mark A.  Greer (Animal
     Creek Technologies).

   - Texas Instruments TWL4030 MADC battery driver by Lukas Märdian and
     Marek Belisko (Golden Delicious Computers).  The driver is used on
     Freerunner GTA04 phones.

  Highlighted fixes and improvements:

   - Suspend/wakeup logic improvements: power supply objects will block
     system suspend until all power supply events are processed.  Thanks
     to Zoran Markovic (Linaro), Arve Hjonnevag and Todd Poynor (Google)"

* tag 'for-v3.12' of git://git.infradead.org/battery-2.6:
  rx51_battery: Fix channel number when reading adc value
  power: Add twl4030_madc battery driver.
  bq24190_charger: Workaround SS definition problem on i386 builds
  power_supply: Prevent suspend until power supply events are processed
  vexpress-poweroff: Should depend on the required infrastructure
  twl4030-charger: Fix compiler warning with regulator_enable()
  rx51_battery: Replace hardcoded channels values.
  bq24190_charger: Add support for TI BQ24190 Battery Charger
  ab8500-charger: We print an unintended error message
  max8925_power: Fix missing of_node_put
  power_supply: Replace strict_strtol() with kstrtol()
  power: Add APM X-Gene system reboot driver
  power_supply: tosa_battery: Get rid of irq_to_gpio usage
  power supply: collie_battery: Convert to use dev_pm_ops
  power_supply: Make goldfish_battery depend on GOLDFISH || COMPILE_TEST
  power: reset: Add msm restart support
  MAINTAINERS: drivers/power: add entry for SmartReflex AVS drivers
MSM Restart Driver
A power supply hold (ps-hold) bit is set to power the msm chipsets.
Clearing that bit allows us to restart/poweroff. The difference
between poweroff and restart is determined by unique power manager IC
settings.
Required Properties:
-compatible: "qcom,pshold"
-reg: Specifies the physical address of the ps-hold register
Example:
restart@fc4ab000 {
compatible = "qcom,pshold";
reg = <0xfc4ab000 0x4>;
};
......@@ -7624,6 +7624,14 @@ S: Maintained
F: Documentation/security/Smack.txt
F: security/smack/
SMARTREFLEX DRIVERS FOR ADAPTIVE VOLTAGE SCALING (AVS)
M: Kevin Hilman <khilman@kernel.org>
M: Nishanth Menon <nm@ti.com>
S: Maintained
F: drivers/power/avs/smartreflex.c
F: include/linux/power/smartreflex.h
L: linux-pm@vger.kernel.org
SMC91x ETHERNET DRIVER
M: Nicolas Pitre <nico@fluxnic.net>
S: Odd Fixes
......
......@@ -216,6 +216,13 @@ config BATTERY_S3C_ADC
help
Say Y here to enable support for iPAQ h1930/h1940/rx1950 battery
config BATTERY_TWL4030_MADC
tristate "TWL4030 MADC battery driver"
depends on TWL4030_MADC
help
Say Y here to enable this dumb driver for batteries managed
through the TWL4030 MADC.
config CHARGER_88PM860X
tristate "Marvell 88PM860x Charger driver"
depends on MFD_88PM860X && BATTERY_88PM860X
......@@ -334,6 +341,12 @@ config CHARGER_BQ2415X
You'll need this driver to charge batteries on e.g. Nokia
RX-51/N900.
config CHARGER_BQ24190
tristate "TI BQ24190 battery charger driver"
depends on I2C && GPIOLIB
help
Say Y to enable support for the TI BQ24190 battery charger.
config CHARGER_SMB347
tristate "Summit Microelectronics SMB347 Battery Charger"
depends on I2C
......@@ -357,7 +370,7 @@ config AB8500_BM
config BATTERY_GOLDFISH
tristate "Goldfish battery driver"
depends on GENERIC_HARDIRQS
depends on GENERIC_HARDIRQS && (GOLDFISH || COMPILE_TEST)
help
Say Y to enable support for the battery and AC power in the
Goldfish emulator.
......
......@@ -34,6 +34,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_S3C_ADC) += s3c_adc_battery.o
obj-$(CONFIG_BATTERY_TWL4030_MADC) += twl4030_madc_battery.o
obj-$(CONFIG_CHARGER_88PM860X) += 88pm860x_charger.o
obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o
......@@ -50,6 +51,7 @@ obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o
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_POWER_AVS) += avs/
obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o
obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o
......
......@@ -774,6 +774,7 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di,
di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5;
dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status,
di->max_usb_in_curr.usb_type_max);
break;
case USB_STAT_NOT_VALID_LINK:
dev_err(di->dev, "USB Type invalid - try charging anyway\n");
di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5;
......
/*
* Driver for the TI bq24190 battery charger.
*
* Author: Mark A. Greer <mgreer@animalcreek.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.
*/
#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/gpio.h>
#include <linux/i2c.h>
#include <linux/power/bq24190_charger.h>
#define BQ24190_MANUFACTURER "Texas Instruments"
#define BQ24190_REG_ISC 0x00 /* Input Source Control */
#define BQ24190_REG_ISC_EN_HIZ_MASK BIT(7)
#define BQ24190_REG_ISC_EN_HIZ_SHIFT 7
#define BQ24190_REG_ISC_VINDPM_MASK (BIT(6) | BIT(5) | BIT(4) | \
BIT(3))
#define BQ24190_REG_ISC_VINDPM_SHIFT 3
#define BQ24190_REG_ISC_IINLIM_MASK (BIT(2) | BIT(1) | BIT(0))
#define BQ24190_REG_ISC_IINLIM_SHIFT 0
#define BQ24190_REG_POC 0x01 /* Power-On Configuration */
#define BQ24190_REG_POC_RESET_MASK BIT(7)
#define BQ24190_REG_POC_RESET_SHIFT 7
#define BQ24190_REG_POC_WDT_RESET_MASK BIT(6)
#define BQ24190_REG_POC_WDT_RESET_SHIFT 6
#define BQ24190_REG_POC_CHG_CONFIG_MASK (BIT(5) | BIT(4))
#define BQ24190_REG_POC_CHG_CONFIG_SHIFT 4
#define BQ24190_REG_POC_SYS_MIN_MASK (BIT(3) | BIT(2) | BIT(1))
#define BQ24190_REG_POC_SYS_MIN_SHIFT 1
#define BQ24190_REG_POC_BOOST_LIM_MASK BIT(0)
#define BQ24190_REG_POC_BOOST_LIM_SHIFT 0
#define BQ24190_REG_CCC 0x02 /* Charge Current Control */
#define BQ24190_REG_CCC_ICHG_MASK (BIT(7) | BIT(6) | BIT(5) | \
BIT(4) | BIT(3) | BIT(2))
#define BQ24190_REG_CCC_ICHG_SHIFT 2
#define BQ24190_REG_CCC_FORCE_20PCT_MASK BIT(0)
#define BQ24190_REG_CCC_FORCE_20PCT_SHIFT 0
#define BQ24190_REG_PCTCC 0x03 /* Pre-charge/Termination Current Cntl */
#define BQ24190_REG_PCTCC_IPRECHG_MASK (BIT(7) | BIT(6) | BIT(5) | \
BIT(4))
#define BQ24190_REG_PCTCC_IPRECHG_SHIFT 4
#define BQ24190_REG_PCTCC_ITERM_MASK (BIT(3) | BIT(2) | BIT(1) | \
BIT(0))
#define BQ24190_REG_PCTCC_ITERM_SHIFT 0
#define BQ24190_REG_CVC 0x04 /* Charge Voltage Control */
#define BQ24190_REG_CVC_VREG_MASK (BIT(7) | BIT(6) | BIT(5) | \
BIT(4) | BIT(3) | BIT(2))
#define BQ24190_REG_CVC_VREG_SHIFT 2
#define BQ24190_REG_CVC_BATLOWV_MASK BIT(1)
#define BQ24190_REG_CVC_BATLOWV_SHIFT 1
#define BQ24190_REG_CVC_VRECHG_MASK BIT(0)
#define BQ24190_REG_CVC_VRECHG_SHIFT 0
#define BQ24190_REG_CTTC 0x05 /* Charge Term/Timer Control */
#define BQ24190_REG_CTTC_EN_TERM_MASK BIT(7)
#define BQ24190_REG_CTTC_EN_TERM_SHIFT 7
#define BQ24190_REG_CTTC_TERM_STAT_MASK BIT(6)
#define BQ24190_REG_CTTC_TERM_STAT_SHIFT 6
#define BQ24190_REG_CTTC_WATCHDOG_MASK (BIT(5) | BIT(4))
#define BQ24190_REG_CTTC_WATCHDOG_SHIFT 4
#define BQ24190_REG_CTTC_EN_TIMER_MASK BIT(3)
#define BQ24190_REG_CTTC_EN_TIMER_SHIFT 3
#define BQ24190_REG_CTTC_CHG_TIMER_MASK (BIT(2) | BIT(1))
#define BQ24190_REG_CTTC_CHG_TIMER_SHIFT 1
#define BQ24190_REG_CTTC_JEITA_ISET_MASK BIT(0)
#define BQ24190_REG_CTTC_JEITA_ISET_SHIFT 0
#define BQ24190_REG_ICTRC 0x06 /* IR Comp/Thermal Regulation Control */
#define BQ24190_REG_ICTRC_BAT_COMP_MASK (BIT(7) | BIT(6) | BIT(5))
#define BQ24190_REG_ICTRC_BAT_COMP_SHIFT 5
#define BQ24190_REG_ICTRC_VCLAMP_MASK (BIT(4) | BIT(3) | BIT(2))
#define BQ24190_REG_ICTRC_VCLAMP_SHIFT 2
#define BQ24190_REG_ICTRC_TREG_MASK (BIT(1) | BIT(0))
#define BQ24190_REG_ICTRC_TREG_SHIFT 0
#define BQ24190_REG_MOC 0x07 /* Misc. Operation Control */
#define BQ24190_REG_MOC_DPDM_EN_MASK BIT(7)
#define BQ24190_REG_MOC_DPDM_EN_SHIFT 7
#define BQ24190_REG_MOC_TMR2X_EN_MASK BIT(6)
#define BQ24190_REG_MOC_TMR2X_EN_SHIFT 6
#define BQ24190_REG_MOC_BATFET_DISABLE_MASK BIT(5)
#define BQ24190_REG_MOC_BATFET_DISABLE_SHIFT 5
#define BQ24190_REG_MOC_JEITA_VSET_MASK BIT(4)
#define BQ24190_REG_MOC_JEITA_VSET_SHIFT 4
#define BQ24190_REG_MOC_INT_MASK_MASK (BIT(1) | BIT(0))
#define BQ24190_REG_MOC_INT_MASK_SHIFT 0
#define BQ24190_REG_SS 0x08 /* System Status */
#define BQ24190_REG_SS_VBUS_STAT_MASK (BIT(7) | BIT(6))
#define BQ24190_REG_SS_VBUS_STAT_SHIFT 6
#define BQ24190_REG_SS_CHRG_STAT_MASK (BIT(5) | BIT(4))
#define BQ24190_REG_SS_CHRG_STAT_SHIFT 4
#define BQ24190_REG_SS_DPM_STAT_MASK BIT(3)
#define BQ24190_REG_SS_DPM_STAT_SHIFT 3
#define BQ24190_REG_SS_PG_STAT_MASK BIT(2)
#define BQ24190_REG_SS_PG_STAT_SHIFT 2
#define BQ24190_REG_SS_THERM_STAT_MASK BIT(1)
#define BQ24190_REG_SS_THERM_STAT_SHIFT 1
#define BQ24190_REG_SS_VSYS_STAT_MASK BIT(0)
#define BQ24190_REG_SS_VSYS_STAT_SHIFT 0
#define BQ24190_REG_F 0x09 /* Fault */
#define BQ24190_REG_F_WATCHDOG_FAULT_MASK BIT(7)
#define BQ24190_REG_F_WATCHDOG_FAULT_SHIFT 7
#define BQ24190_REG_F_BOOST_FAULT_MASK BIT(6)
#define BQ24190_REG_F_BOOST_FAULT_SHIFT 6
#define BQ24190_REG_F_CHRG_FAULT_MASK (BIT(5) | BIT(4))
#define BQ24190_REG_F_CHRG_FAULT_SHIFT 4
#define BQ24190_REG_F_BAT_FAULT_MASK BIT(3)
#define BQ24190_REG_F_BAT_FAULT_SHIFT 3
#define BQ24190_REG_F_NTC_FAULT_MASK (BIT(2) | BIT(1) | BIT(0))
#define BQ24190_REG_F_NTC_FAULT_SHIFT 0
#define BQ24190_REG_VPRS 0x0A /* Vendor/Part/Revision Status */
#define BQ24190_REG_VPRS_PN_MASK (BIT(5) | BIT(4) | BIT(3))
#define BQ24190_REG_VPRS_PN_SHIFT 3
#define BQ24190_REG_VPRS_PN_24190 0x4
#define BQ24190_REG_VPRS_PN_24192 0x5 /* Also 24193 */
#define BQ24190_REG_VPRS_PN_24192I 0x3
#define BQ24190_REG_VPRS_TS_PROFILE_MASK BIT(2)
#define BQ24190_REG_VPRS_TS_PROFILE_SHIFT 2
#define BQ24190_REG_VPRS_DEV_REG_MASK (BIT(1) | BIT(0))
#define BQ24190_REG_VPRS_DEV_REG_SHIFT 0
/*
* The FAULT register is latched by the bq24190 (except for NTC_FAULT)
* so the first read after a fault returns the latched value and subsequent
* reads return the current value. In order to return the fault status
* to the user, have the interrupt handler save the reg's value and retrieve
* it in the appropriate health/status routine. Each routine has its own
* flag indicating whether it should use the value stored by the last run
* of the interrupt handler or do an actual reg read. That way each routine
* can report back whatever fault may have occured.
*/
struct bq24190_dev_info {
struct i2c_client *client;
struct device *dev;
struct power_supply charger;
struct power_supply battery;
char model_name[I2C_NAME_SIZE];
kernel_ulong_t model;
unsigned int gpio_int;
unsigned int irq;
struct mutex f_reg_lock;
bool first_time;
bool charger_health_valid;
bool battery_health_valid;
bool battery_status_valid;
u8 f_reg;
u8 ss_reg;
u8 watchdog;
};
/*
* The tables below provide a 2-way mapping for the value that goes in
* the register field and the real-world value that it represents.
* The index of the array is the value that goes in the register; the
* number at that index in the array is the real-world value that it
* represents.
*/
/* REG02[7:2] (ICHG) in uAh */
static const int bq24190_ccc_ichg_values[] = {
512000, 576000, 640000, 704000, 768000, 832000, 896000, 960000,
1024000, 1088000, 1152000, 1216000, 1280000, 1344000, 1408000, 1472000,
1536000, 1600000, 1664000, 1728000, 1792000, 1856000, 1920000, 1984000,
2048000, 2112000, 2176000, 2240000, 2304000, 2368000, 2432000, 2496000,
2560000, 2624000, 2688000, 2752000, 2816000, 2880000, 2944000, 3008000,
3072000, 3136000, 3200000, 3264000, 3328000, 3392000, 3456000, 3520000,
3584000, 3648000, 3712000, 3776000, 3840000, 3904000, 3968000, 4032000,
4096000, 4160000, 4224000, 4288000, 4352000, 4416000, 4480000, 4544000
};
/* REG04[7:2] (VREG) in uV */
static const int bq24190_cvc_vreg_values[] = {
3504000, 3520000, 3536000, 3552000, 3568000, 3584000, 3600000, 3616000,
3632000, 3648000, 3664000, 3680000, 3696000, 3712000, 3728000, 3744000,
3760000, 3776000, 3792000, 3808000, 3824000, 3840000, 3856000, 3872000,
3888000, 3904000, 3920000, 3936000, 3952000, 3968000, 3984000, 4000000,
4016000, 4032000, 4048000, 4064000, 4080000, 4096000, 4112000, 4128000,
4144000, 4160000, 4176000, 4192000, 4208000, 4224000, 4240000, 4256000,
4272000, 4288000, 4304000, 4320000, 4336000, 4352000, 4368000, 4384000,
4400000
};
/* REG06[1:0] (TREG) in tenths of degrees Celcius */
static const int bq24190_ictrc_treg_values[] = {
600, 800, 1000, 1200
};
/*
* Return the index in 'tbl' of greatest value that is less than or equal to
* 'val'. The index range returned is 0 to 'tbl_size' - 1. Assumes that
* the values in 'tbl' are sorted from smallest to largest and 'tbl_size'
* is less than 2^8.
*/
static u8 bq24190_find_idx(const int tbl[], int tbl_size, int v)
{
int i;
for (i = 1; i < tbl_size; i++)
if (v < tbl[i])
break;
return i - 1;
}
/* Basic driver I/O routines */
static int bq24190_read(struct bq24190_dev_info *bdi, u8 reg, u8 *data)
{
int ret;
ret = i2c_smbus_read_byte_data(bdi->client, reg);
if (ret < 0)
return ret;
*data = ret;
return 0;
}
static int bq24190_write(struct bq24190_dev_info *bdi, u8 reg, u8 data)
{
return i2c_smbus_write_byte_data(bdi->client, reg, data);
}
static int bq24190_read_mask(struct bq24190_dev_info *bdi, u8 reg,
u8 mask, u8 shift, u8 *data)
{
u8 v;
int ret;
ret = bq24190_read(bdi, reg, &v);
if (ret < 0)
return ret;
v &= mask;
v >>= shift;
*data = v;
return 0;
}
static int bq24190_write_mask(struct bq24190_dev_info *bdi, u8 reg,
u8 mask, u8 shift, u8 data)
{
u8 v;
int ret;
ret = bq24190_read(bdi, reg, &v);
if (ret < 0)
return ret;
v &= ~mask;
v |= ((data << shift) & mask);
return bq24190_write(bdi, reg, v);
}
static int bq24190_get_field_val(struct bq24190_dev_info *bdi,
u8 reg, u8 mask, u8 shift,
const int tbl[], int tbl_size,
int *val)
{
u8 v;
int ret;
ret = bq24190_read_mask(bdi, reg, mask, shift, &v);
if (ret < 0)
return ret;
v = (v >= tbl_size) ? (tbl_size - 1) : v;
*val = tbl[v];
return 0;
}
static int bq24190_set_field_val(struct bq24190_dev_info *bdi,
u8 reg, u8 mask, u8 shift,
const int tbl[], int tbl_size,
int val)
{
u8 idx;
idx = bq24190_find_idx(tbl, tbl_size, val);
return bq24190_write_mask(bdi, reg, mask, shift, idx);
}
#ifdef CONFIG_SYSFS
/*
* There are a numerous options that are configurable on the bq24190
* that go well beyond what the power_supply properties provide access to.
* Provide sysfs access to them so they can be examined and possibly modified
* on the fly. They will be provided for the charger power_supply object only
* and will be prefixed by 'f_' to make them easier to recognize.
*/
#define BQ24190_SYSFS_FIELD(_name, r, f, m, store) \
{ \
.attr = __ATTR(f_##_name, m, bq24190_sysfs_show, store), \
.reg = BQ24190_REG_##r, \
.mask = BQ24190_REG_##r##_##f##_MASK, \
.shift = BQ24190_REG_##r##_##f##_SHIFT, \
}
#define BQ24190_SYSFS_FIELD_RW(_name, r, f) \
BQ24190_SYSFS_FIELD(_name, r, f, S_IWUSR | S_IRUGO, \
bq24190_sysfs_store)
#define BQ24190_SYSFS_FIELD_RO(_name, r, f) \
BQ24190_SYSFS_FIELD(_name, r, f, S_IRUGO, NULL)
static ssize_t bq24190_sysfs_show(struct device *dev,
struct device_attribute *attr, char *buf);
static ssize_t bq24190_sysfs_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count);
struct bq24190_sysfs_field_info {
struct device_attribute attr;
u8 reg;
u8 mask;
u8 shift;
};
/* On i386 ptrace-abi.h defines SS that breaks the macro calls below. */
#undef SS
static struct bq24190_sysfs_field_info bq24190_sysfs_field_tbl[] = {
/* sysfs name reg field in reg */
BQ24190_SYSFS_FIELD_RW(en_hiz, ISC, EN_HIZ),
BQ24190_SYSFS_FIELD_RW(vindpm, ISC, VINDPM),
BQ24190_SYSFS_FIELD_RW(iinlim, ISC, IINLIM),
BQ24190_SYSFS_FIELD_RW(chg_config, POC, CHG_CONFIG),
BQ24190_SYSFS_FIELD_RW(sys_min, POC, SYS_MIN),
BQ24190_SYSFS_FIELD_RW(boost_lim, POC, BOOST_LIM),
BQ24190_SYSFS_FIELD_RW(ichg, CCC, ICHG),
BQ24190_SYSFS_FIELD_RW(force_20_pct, CCC, FORCE_20PCT),
BQ24190_SYSFS_FIELD_RW(iprechg, PCTCC, IPRECHG),
BQ24190_SYSFS_FIELD_RW(iterm, PCTCC, ITERM),
BQ24190_SYSFS_FIELD_RW(vreg, CVC, VREG),
BQ24190_SYSFS_FIELD_RW(batlowv, CVC, BATLOWV),
BQ24190_SYSFS_FIELD_RW(vrechg, CVC, VRECHG),
BQ24190_SYSFS_FIELD_RW(en_term, CTTC, EN_TERM),
BQ24190_SYSFS_FIELD_RW(term_stat, CTTC, TERM_STAT),
BQ24190_SYSFS_FIELD_RO(watchdog, CTTC, WATCHDOG),
BQ24190_SYSFS_FIELD_RW(en_timer, CTTC, EN_TIMER),
BQ24190_SYSFS_FIELD_RW(chg_timer, CTTC, CHG_TIMER),
BQ24190_SYSFS_FIELD_RW(jeta_iset, CTTC, JEITA_ISET),
BQ24190_SYSFS_FIELD_RW(bat_comp, ICTRC, BAT_COMP),
BQ24190_SYSFS_FIELD_RW(vclamp, ICTRC, VCLAMP),
BQ24190_SYSFS_FIELD_RW(treg, ICTRC, TREG),
BQ24190_SYSFS_FIELD_RW(dpdm_en, MOC, DPDM_EN),
BQ24190_SYSFS_FIELD_RW(tmr2x_en, MOC, TMR2X_EN),
BQ24190_SYSFS_FIELD_RW(batfet_disable, MOC, BATFET_DISABLE),
BQ24190_SYSFS_FIELD_RW(jeita_vset, MOC, JEITA_VSET),
BQ24190_SYSFS_FIELD_RO(int_mask, MOC, INT_MASK),
BQ24190_SYSFS_FIELD_RO(vbus_stat, SS, VBUS_STAT),
BQ24190_SYSFS_FIELD_RO(chrg_stat, SS, CHRG_STAT),
BQ24190_SYSFS_FIELD_RO(dpm_stat, SS, DPM_STAT),
BQ24190_SYSFS_FIELD_RO(pg_stat, SS, PG_STAT),
BQ24190_SYSFS_FIELD_RO(therm_stat, SS, THERM_STAT),
BQ24190_SYSFS_FIELD_RO(vsys_stat, SS, VSYS_STAT),
BQ24190_SYSFS_FIELD_RO(watchdog_fault, F, WATCHDOG_FAULT),
BQ24190_SYSFS_FIELD_RO(boost_fault, F, BOOST_FAULT),
BQ24190_SYSFS_FIELD_RO(chrg_fault, F, CHRG_FAULT),
BQ24190_SYSFS_FIELD_RO(bat_fault, F, BAT_FAULT),
BQ24190_SYSFS_FIELD_RO(ntc_fault, F, NTC_FAULT),
BQ24190_SYSFS_FIELD_RO(pn, VPRS, PN),
BQ24190_SYSFS_FIELD_RO(ts_profile, VPRS, TS_PROFILE),
BQ24190_SYSFS_FIELD_RO(dev_reg, VPRS, DEV_REG),
};
static struct attribute *
bq24190_sysfs_attrs[ARRAY_SIZE(bq24190_sysfs_field_tbl) + 1];
static const struct attribute_group bq24190_sysfs_attr_group = {
.attrs = bq24190_sysfs_attrs,
};
static void bq24190_sysfs_init_attrs(void)
{
int i, limit = ARRAY_SIZE(bq24190_sysfs_field_tbl);
for (i = 0; i < limit; i++)
bq24190_sysfs_attrs[i] = &bq24190_sysfs_field_tbl[i].attr.attr;
bq24190_sysfs_attrs[limit] = NULL; /* Has additional entry for this */
}
static struct bq24190_sysfs_field_info *bq24190_sysfs_field_lookup(
const char *name)
{
int i, limit = ARRAY_SIZE(bq24190_sysfs_field_tbl);
for (i = 0; i < limit; i++)
if (!strcmp(name, bq24190_sysfs_field_tbl[i].attr.attr.name))
break;
if (i >= limit)
return NULL;
return &bq24190_sysfs_field_tbl[i];
}
static ssize_t bq24190_sysfs_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct power_supply *psy = dev_get_drvdata(dev);
struct bq24190_dev_info *bdi =
container_of(psy, struct bq24190_dev_info, charger);
struct bq24190_sysfs_field_info *info;
int ret;
u8 v;
info = bq24190_sysfs_field_lookup(attr->attr.name);
if (!info)
return -EINVAL;
ret = bq24190_read_mask(bdi, info->reg, info->mask, info->shift, &v);
if (ret)
return ret;
return scnprintf(buf, PAGE_SIZE, "%hhx\n", v);
}
static ssize_t bq24190_sysfs_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct power_supply *psy = dev_get_drvdata(dev);
struct bq24190_dev_info *bdi =
container_of(psy, struct bq24190_dev_info, charger);
struct bq24190_sysfs_field_info *info;
int ret;
u8 v;
info = bq24190_sysfs_field_lookup(attr->attr.name);
if (!info)
return -EINVAL;
ret = kstrtou8(buf, 0, &v);
if (ret < 0)
return ret;
ret = bq24190_write_mask(bdi, info->reg, info->mask, info->shift, v);
if (ret)
return ret;
return count;
}
static int bq24190_sysfs_create_group(struct bq24190_dev_info *bdi)
{
bq24190_sysfs_init_attrs();
return sysfs_create_group(&bdi->charger.dev->kobj,
&bq24190_sysfs_attr_group);
}
static void bq24190_sysfs_remove_group(struct bq24190_dev_info *bdi)
{
sysfs_remove_group(&bdi->charger.dev->kobj, &bq24190_sysfs_attr_group);
}
#else
static int bq24190_sysfs_create_group(struct bq24190_dev_info *bdi)
{
return 0;
}
static inline void bq24190_sysfs_remove_group(struct bq24190_dev_info *bdi) {}
#endif
/*
* According to the "Host Mode and default Mode" section of the
* manual, a write to any register causes the bq24190 to switch
* from default mode to host mode. It will switch back to default
* mode after a WDT timeout unless the WDT is turned off as well.
* So, by simply turning off the WDT, we accomplish both with the
* same write.
*/
static int bq24190_set_mode_host(struct bq24190_dev_info *bdi)
{
int ret;
u8 v;
ret = bq24190_read(bdi, BQ24190_REG_CTTC, &v);
if (ret < 0)
return ret;
bdi->watchdog = ((v & BQ24190_REG_CTTC_WATCHDOG_MASK) >>
BQ24190_REG_CTTC_WATCHDOG_SHIFT);
v &= ~BQ24190_REG_CTTC_WATCHDOG_MASK;
return bq24190_write(bdi, BQ24190_REG_CTTC, v);
}
static int bq24190_register_reset(struct bq24190_dev_info *bdi)
{
int ret, limit = 100;
u8 v;
/* Reset the registers */
ret = bq24190_write_mask(bdi, BQ24190_REG_POC,
BQ24190_REG_POC_RESET_MASK,
BQ24190_REG_POC_RESET_SHIFT,
0x1);
if (ret < 0)
return ret;
/* Reset bit will be cleared by hardware so poll until it is */
do {
ret = bq24190_read_mask(bdi, BQ24190_REG_POC,
BQ24190_REG_POC_RESET_MASK,
BQ24190_REG_POC_RESET_SHIFT,
&v);
if (ret < 0)
return ret;
if (!v)
break;
udelay(10);
} while (--limit);
if (!limit)
return -EIO;
return 0;
}
/* Charger power supply property routines */
static int bq24190_charger_get_charge_type(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
u8 v;
int type, ret;
ret = bq24190_read_mask(bdi, BQ24190_REG_POC,
BQ24190_REG_POC_CHG_CONFIG_MASK,
BQ24190_REG_POC_CHG_CONFIG_SHIFT,
&v);
if (ret < 0)
return ret;
/* If POC[CHG_CONFIG] (REG01[5:4]) == 0, charge is disabled */
if (!v) {
type = POWER_SUPPLY_CHARGE_TYPE_NONE;
} else {
ret = bq24190_read_mask(bdi, BQ24190_REG_CCC,
BQ24190_REG_CCC_FORCE_20PCT_MASK,
BQ24190_REG_CCC_FORCE_20PCT_SHIFT,
&v);
if (ret < 0)
return ret;
type = (v) ? POWER_SUPPLY_CHARGE_TYPE_TRICKLE :
POWER_SUPPLY_CHARGE_TYPE_FAST;
}
val->intval = type;
return 0;
}
static int bq24190_charger_set_charge_type(struct bq24190_dev_info *bdi,
const union power_supply_propval *val)
{
u8 chg_config, force_20pct, en_term;
int ret;
/*
* According to the "Termination when REG02[0] = 1" section of
* the bq24190 manual, the trickle charge could be less than the
* termination current so it recommends turning off the termination
* function.
*
* Note: AFAICT from the datasheet, the user will have to manually
* turn off the charging when in 20% mode. If its not turned off,
* there could be battery damage. So, use this mode at your own risk.
*/
switch (val->intval) {
case POWER_SUPPLY_CHARGE_TYPE_NONE:
chg_config = 0x0;
break;
case POWER_SUPPLY_CHARGE_TYPE_TRICKLE:
chg_config = 0x1;
force_20pct = 0x1;
en_term = 0x0;
break;
case POWER_SUPPLY_CHARGE_TYPE_FAST:
chg_config = 0x1;
force_20pct = 0x0;
en_term = 0x1;
break;
default:
return -EINVAL;
}
if (chg_config) { /* Enabling the charger */
ret = bq24190_write_mask(bdi, BQ24190_REG_CCC,
BQ24190_REG_CCC_FORCE_20PCT_MASK,
BQ24190_REG_CCC_FORCE_20PCT_SHIFT,
force_20pct);
if (ret < 0)
return ret;
ret = bq24190_write_mask(bdi, BQ24190_REG_CTTC,
BQ24190_REG_CTTC_EN_TERM_MASK,
BQ24190_REG_CTTC_EN_TERM_SHIFT,
en_term);
if (ret < 0)
return ret;
}
return bq24190_write_mask(bdi, BQ24190_REG_POC,
BQ24190_REG_POC_CHG_CONFIG_MASK,
BQ24190_REG_POC_CHG_CONFIG_SHIFT, chg_config);
}
static int bq24190_charger_get_health(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
u8 v;
int health, ret;
mutex_lock(&bdi->f_reg_lock);
if (bdi->charger_health_valid) {
v = bdi->f_reg;
bdi->charger_health_valid = false;
mutex_unlock(&bdi->f_reg_lock);
} else {
mutex_unlock(&bdi->f_reg_lock);
ret = bq24190_read(bdi, BQ24190_REG_F, &v);
if (ret < 0)
return ret;
}
if (v & BQ24190_REG_F_BOOST_FAULT_MASK) {
/*
* This could be over-current or over-voltage but there's
* no way to tell which. Return 'OVERVOLTAGE' since there
* isn't an 'OVERCURRENT' value defined that we can return
* even if it was over-current.
*/
health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
} else {
v &= BQ24190_REG_F_CHRG_FAULT_MASK;
v >>= BQ24190_REG_F_CHRG_FAULT_SHIFT;
switch (v) {
case 0x0: /* Normal */
health = POWER_SUPPLY_HEALTH_GOOD;
break;
case 0x1: /* Input Fault (VBUS OVP or VBAT<VBUS<3.8V) */
/*
* This could be over-voltage or under-voltage
* and there's no way to tell which. Instead
* of looking foolish and returning 'OVERVOLTAGE'
* when its really under-voltage, just return
* 'UNSPEC_FAILURE'.
*/
health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
break;
case 0x2: /* Thermal Shutdown */
health = POWER_SUPPLY_HEALTH_OVERHEAT;
break;
case 0x3: /* Charge Safety Timer Expiration */
health = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
break;
default:
health = POWER_SUPPLY_HEALTH_UNKNOWN;
}
}
val->intval = health;
return 0;
}
static int bq24190_charger_get_online(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
u8 v;
int ret;
ret = bq24190_read_mask(bdi, BQ24190_REG_SS,
BQ24190_REG_SS_PG_STAT_MASK,
BQ24190_REG_SS_PG_STAT_SHIFT, &v);
if (ret < 0)
return ret;
val->intval = v;
return 0;
}
static int bq24190_charger_get_current(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
u8 v;
int curr, ret;
ret = bq24190_get_field_val(bdi, BQ24190_REG_CCC,
BQ24190_REG_CCC_ICHG_MASK, BQ24190_REG_CCC_ICHG_SHIFT,
bq24190_ccc_ichg_values,
ARRAY_SIZE(bq24190_ccc_ichg_values), &curr);
if (ret < 0)
return ret;
ret = bq24190_read_mask(bdi, BQ24190_REG_CCC,
BQ24190_REG_CCC_FORCE_20PCT_MASK,
BQ24190_REG_CCC_FORCE_20PCT_SHIFT, &v);
if (ret < 0)
return ret;
/* If FORCE_20PCT is enabled, then current is 20% of ICHG value */
if (v)
curr /= 5;
val->intval = curr;
return 0;
}
static int bq24190_charger_get_current_max(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
int idx = ARRAY_SIZE(bq24190_ccc_ichg_values) - 1;
val->intval = bq24190_ccc_ichg_values[idx];
return 0;
}
static int bq24190_charger_set_current(struct bq24190_dev_info *bdi,
const union power_supply_propval *val)
{
u8 v;
int ret, curr = val->intval;
ret = bq24190_read_mask(bdi, BQ24190_REG_CCC,
BQ24190_REG_CCC_FORCE_20PCT_MASK,
BQ24190_REG_CCC_FORCE_20PCT_SHIFT, &v);
if (ret < 0)
return ret;
/* If FORCE_20PCT is enabled, have to multiply value passed in by 5 */
if (v)
curr *= 5;
return bq24190_set_field_val(bdi, BQ24190_REG_CCC,
BQ24190_REG_CCC_ICHG_MASK, BQ24190_REG_CCC_ICHG_SHIFT,
bq24190_ccc_ichg_values,
ARRAY_SIZE(bq24190_ccc_ichg_values), curr);
}
static int bq24190_charger_get_voltage(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
int voltage, ret;
ret = bq24190_get_field_val(bdi, BQ24190_REG_CVC,
BQ24190_REG_CVC_VREG_MASK, BQ24190_REG_CVC_VREG_SHIFT,
bq24190_cvc_vreg_values,
ARRAY_SIZE(bq24190_cvc_vreg_values), &voltage);
if (ret < 0)
return ret;
val->intval = voltage;
return 0;
}
static int bq24190_charger_get_voltage_max(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
int idx = ARRAY_SIZE(bq24190_cvc_vreg_values) - 1;
val->intval = bq24190_cvc_vreg_values[idx];
return 0;
}
static int bq24190_charger_set_voltage(struct bq24190_dev_info *bdi,
const union power_supply_propval *val)
{
return bq24190_set_field_val(bdi, BQ24190_REG_CVC,
BQ24190_REG_CVC_VREG_MASK, BQ24190_REG_CVC_VREG_SHIFT,
bq24190_cvc_vreg_values,
ARRAY_SIZE(bq24190_cvc_vreg_values), val->intval);
}
static int bq24190_charger_get_property(struct power_supply *psy,
enum power_supply_property psp, union power_supply_propval *val)
{
struct bq24190_dev_info *bdi =
container_of(psy, struct bq24190_dev_info, charger);
int ret;
dev_dbg(bdi->dev, "prop: %d\n", psp);
pm_runtime_get_sync(bdi->dev);
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_TYPE:
ret = bq24190_charger_get_charge_type(bdi, val);
break;
case POWER_SUPPLY_PROP_HEALTH:
ret = bq24190_charger_get_health(bdi, val);
break;
case POWER_SUPPLY_PROP_ONLINE:
ret = bq24190_charger_get_online(bdi, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
ret = bq24190_charger_get_current(bdi, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
ret = bq24190_charger_get_current_max(bdi, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
ret = bq24190_charger_get_voltage(bdi, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
ret = bq24190_charger_get_voltage_max(bdi, val);
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
ret = 0;
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = bdi->model_name;
ret = 0;
break;
case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = BQ24190_MANUFACTURER;
ret = 0;
break;
default:
ret = -ENODATA;
}
pm_runtime_put_sync(bdi->dev);
return ret;
}
static int bq24190_charger_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct bq24190_dev_info *bdi =
container_of(psy, struct bq24190_dev_info, charger);
int ret;
dev_dbg(bdi->dev, "prop: %d\n", psp);
pm_runtime_get_sync(bdi->dev);
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_TYPE:
ret = bq24190_charger_set_charge_type(bdi, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
ret = bq24190_charger_set_current(bdi, val);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
ret = bq24190_charger_set_voltage(bdi, val);
break;
default:
ret = -EINVAL;
}
pm_runtime_put_sync(bdi->dev);
return ret;
}
static int bq24190_charger_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
int ret;
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_TYPE:
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 bq24190_charger_properties[] = {
POWER_SUPPLY_PROP_TYPE,
POWER_SUPPLY_PROP_HEALTH,
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_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
};
static char *bq24190_charger_supplied_to[] = {
"main-battery",
};
static void bq24190_charger_init(struct power_supply *charger)
{
charger->name = "bq24190-charger";
charger->type = POWER_SUPPLY_TYPE_USB;
charger->properties = bq24190_charger_properties;
charger->num_properties = ARRAY_SIZE(bq24190_charger_properties);
charger->supplied_to = bq24190_charger_supplied_to;
charger->num_supplies = ARRAY_SIZE(bq24190_charger_supplied_to);
charger->get_property = bq24190_charger_get_property;
charger->set_property = bq24190_charger_set_property;
charger->property_is_writeable = bq24190_charger_property_is_writeable;
}
/* Battery power supply property routines */
static int bq24190_battery_get_status(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
u8 ss_reg, chrg_fault;
int status, ret;
mutex_lock(&bdi->f_reg_lock);
if (bdi->battery_status_valid) {
chrg_fault = bdi->f_reg;
bdi->battery_status_valid = false;
mutex_unlock(&bdi->f_reg_lock);
} else {
mutex_unlock(&bdi->f_reg_lock);
ret = bq24190_read(bdi, BQ24190_REG_F, &chrg_fault);
if (ret < 0)
return ret;
}
chrg_fault &= BQ24190_REG_F_CHRG_FAULT_MASK;
chrg_fault >>= BQ24190_REG_F_CHRG_FAULT_SHIFT;
ret = bq24190_read(bdi, BQ24190_REG_SS, &ss_reg);
if (ret < 0)
return ret;
/*
* The battery must be discharging when any of these are true:
* - there is no good power source;
* - there is a charge fault.
* Could also be discharging when in "supplement mode" but
* there is no way to tell when its in that mode.
*/
if (!(ss_reg & BQ24190_REG_SS_PG_STAT_MASK) || chrg_fault) {
status = POWER_SUPPLY_STATUS_DISCHARGING;
} else {
ss_reg &= BQ24190_REG_SS_CHRG_STAT_MASK;
ss_reg >>= BQ24190_REG_SS_CHRG_STAT_SHIFT;
switch (ss_reg) {
case 0x0: /* Not Charging */
status = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
case 0x1: /* Pre-charge */
case 0x2: /* Fast Charging */
status = POWER_SUPPLY_STATUS_CHARGING;
break;
case 0x3: /* Charge Termination Done */
status = POWER_SUPPLY_STATUS_FULL;
break;
default:
ret = -EIO;
}
}
if (!ret)
val->intval = status;
return ret;
}
static int bq24190_battery_get_health(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
u8 v;
int health, ret;
mutex_lock(&bdi->f_reg_lock);
if (bdi->battery_health_valid) {
v = bdi->f_reg;
bdi->battery_health_valid = false;
mutex_unlock(&bdi->f_reg_lock);
} else {
mutex_unlock(&bdi->f_reg_lock);
ret = bq24190_read(bdi, BQ24190_REG_F, &v);
if (ret < 0)
return ret;
}
if (v & BQ24190_REG_F_BAT_FAULT_MASK) {
health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
} else {
v &= BQ24190_REG_F_NTC_FAULT_MASK;
v >>= BQ24190_REG_F_NTC_FAULT_SHIFT;
switch (v) {
case 0x0: /* Normal */
health = POWER_SUPPLY_HEALTH_GOOD;
break;
case 0x1: /* TS1 Cold */
case 0x3: /* TS2 Cold */
case 0x5: /* Both Cold */
health = POWER_SUPPLY_HEALTH_COLD;
break;
case 0x2: /* TS1 Hot */
case 0x4: /* TS2 Hot */
case 0x6: /* Both Hot */
health = POWER_SUPPLY_HEALTH_OVERHEAT;
break;
default:
health = POWER_SUPPLY_HEALTH_UNKNOWN;
}
}
val->intval = health;
return 0;
}
static int bq24190_battery_get_online(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
u8 batfet_disable;
int ret;
ret = bq24190_read_mask(bdi, BQ24190_REG_MOC,
BQ24190_REG_MOC_BATFET_DISABLE_MASK,
BQ24190_REG_MOC_BATFET_DISABLE_SHIFT, &batfet_disable);
if (ret < 0)
return ret;
val->intval = !batfet_disable;
return 0;
}
static int bq24190_battery_set_online(struct bq24190_dev_info *bdi,
const union power_supply_propval *val)
{
return bq24190_write_mask(bdi, BQ24190_REG_MOC,
BQ24190_REG_MOC_BATFET_DISABLE_MASK,
BQ24190_REG_MOC_BATFET_DISABLE_SHIFT, !val->intval);
}
static int bq24190_battery_get_temp_alert_max(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
int temp, ret;
ret = bq24190_get_field_val(bdi, BQ24190_REG_ICTRC,
BQ24190_REG_ICTRC_TREG_MASK,
BQ24190_REG_ICTRC_TREG_SHIFT,
bq24190_ictrc_treg_values,
ARRAY_SIZE(bq24190_ictrc_treg_values), &temp);
if (ret < 0)
return ret;
val->intval = temp;
return 0;
}
static int bq24190_battery_set_temp_alert_max(struct bq24190_dev_info *bdi,
const union power_supply_propval *val)
{
return bq24190_set_field_val(bdi, BQ24190_REG_ICTRC,
BQ24190_REG_ICTRC_TREG_MASK,
BQ24190_REG_ICTRC_TREG_SHIFT,
bq24190_ictrc_treg_values,
ARRAY_SIZE(bq24190_ictrc_treg_values), val->intval);
}
static int bq24190_battery_get_property(struct power_supply *psy,
enum power_supply_property psp, union power_supply_propval *val)
{
struct bq24190_dev_info *bdi =
container_of(psy, struct bq24190_dev_info, battery);
int ret;
dev_dbg(bdi->dev, "prop: %d\n", psp);
pm_runtime_get_sync(bdi->dev);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
ret = bq24190_battery_get_status(bdi, val);
break;
case POWER_SUPPLY_PROP_HEALTH:
ret = bq24190_battery_get_health(bdi, val);
break;
case POWER_SUPPLY_PROP_ONLINE:
ret = bq24190_battery_get_online(bdi, val);
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
/* Could be Li-on or Li-polymer but no way to tell which */
val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
ret = 0;
break;
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
ret = bq24190_battery_get_temp_alert_max(bdi, val);
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
ret = 0;
break;
default:
ret = -ENODATA;
}
pm_runtime_put_sync(bdi->dev);
return ret;
}
static int bq24190_battery_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct bq24190_dev_info *bdi =
container_of(psy, struct bq24190_dev_info, battery);
int ret;
dev_dbg(bdi->dev, "prop: %d\n", psp);
pm_runtime_put_sync(bdi->dev);
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
ret = bq24190_battery_set_online(bdi, val);
break;
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
ret = bq24190_battery_set_temp_alert_max(bdi, val);
break;
default:
ret = -EINVAL;
}
pm_runtime_put_sync(bdi->dev);
return ret;
}
static int bq24190_battery_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
int ret;
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
ret = 1;
break;
default:
ret = 0;
}
return ret;
}
static enum power_supply_property bq24190_battery_properties[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
POWER_SUPPLY_PROP_SCOPE,
};
static void bq24190_battery_init(struct power_supply *battery)
{
battery->name = "bq24190-battery";
battery->type = POWER_SUPPLY_TYPE_BATTERY;
battery->properties = bq24190_battery_properties;
battery->num_properties = ARRAY_SIZE(bq24190_battery_properties);
battery->get_property = bq24190_battery_get_property;
battery->set_property = bq24190_battery_set_property;
battery->property_is_writeable = bq24190_battery_property_is_writeable;
}
static irqreturn_t bq24190_irq_handler_thread(int irq, void *data)
{
struct bq24190_dev_info *bdi = data;
bool alert_userspace = false;
u8 ss_reg, f_reg;
int ret;
pm_runtime_get_sync(bdi->dev);
ret = bq24190_read(bdi, BQ24190_REG_SS, &ss_reg);
if (ret < 0) {
dev_err(bdi->dev, "Can't read SS reg: %d\n", ret);
goto out;
}
if (ss_reg != bdi->ss_reg) {
/*
* The device is in host mode so when PG_STAT goes from 1->0
* (i.e., power removed) HIZ needs to be disabled.
*/
if ((bdi->ss_reg & BQ24190_REG_SS_PG_STAT_MASK) &&
!(ss_reg & BQ24190_REG_SS_PG_STAT_MASK)) {
ret = bq24190_write_mask(bdi, BQ24190_REG_ISC,
BQ24190_REG_ISC_EN_HIZ_MASK,
BQ24190_REG_ISC_EN_HIZ_SHIFT,
0);
if (ret < 0)
dev_err(bdi->dev, "Can't access ISC reg: %d\n",
ret);
}
bdi->ss_reg = ss_reg;
alert_userspace = true;
}
mutex_lock(&bdi->f_reg_lock);
ret = bq24190_read(bdi, BQ24190_REG_F, &f_reg);
if (ret < 0) {
mutex_unlock(&bdi->f_reg_lock);
dev_err(bdi->dev, "Can't read F reg: %d\n", ret);
goto out;
}
if (f_reg != bdi->f_reg) {
bdi->f_reg = f_reg;
bdi->charger_health_valid = true;
bdi->battery_health_valid = true;
bdi->battery_status_valid = true;
alert_userspace = true;
}
mutex_unlock(&bdi->f_reg_lock);
/*
* Sometimes bq24190 gives a steady trickle of interrupts even
* though the watchdog timer is turned off and neither the STATUS
* nor FAULT registers have changed. Weed out these sprurious
* interrupts so userspace isn't alerted for no reason.
* In addition, the chip always generates an interrupt after
* register reset so we should ignore that one (the very first
* interrupt received).
*/
if (alert_userspace && !bdi->first_time) {
power_supply_changed(&bdi->charger);
power_supply_changed(&bdi->battery);
bdi->first_time = false;
}
out:
pm_runtime_put_sync(bdi->dev);
dev_dbg(bdi->dev, "ss_reg: 0x%02x, f_reg: 0x%02x\n", ss_reg, f_reg);
return IRQ_HANDLED;
}
static int bq24190_hw_init(struct bq24190_dev_info *bdi)
{
u8 v;
int ret;
pm_runtime_get_sync(bdi->dev);
/* First check that the device really is what its supposed to be */
ret = bq24190_read_mask(bdi, BQ24190_REG_VPRS,
BQ24190_REG_VPRS_PN_MASK,
BQ24190_REG_VPRS_PN_SHIFT,
&v);
if (ret < 0)
goto out;
if (v != bdi->model) {
ret = -ENODEV;
goto out;
}
ret = bq24190_register_reset(bdi);
if (ret < 0)
goto out;
ret = bq24190_set_mode_host(bdi);
out:
pm_runtime_put_sync(bdi->dev);
return ret;
}
#ifdef CONFIG_OF
static int bq24190_setup_dt(struct bq24190_dev_info *bdi)
{
bdi->irq = irq_of_parse_and_map(bdi->dev->of_node, 0);
if (bdi->irq <= 0)
return -1;
return 0;
}
#else
static int bq24190_setup_dt(struct bq24190_dev_info *bdi)
{
return -1;
}
#endif
static int bq24190_setup_pdata(struct bq24190_dev_info *bdi,
struct bq24190_platform_data *pdata)
{
int ret;
if (!gpio_is_valid(pdata->gpio_int))
return -1;
ret = gpio_request(pdata->gpio_int, dev_name(bdi->dev));
if (ret < 0)
return -1;
ret = gpio_direction_input(pdata->gpio_int);
if (ret < 0)
goto out;
bdi->irq = gpio_to_irq(pdata->gpio_int);
if (!bdi->irq)
goto out;
bdi->gpio_int = pdata->gpio_int;
return 0;
out:
gpio_free(pdata->gpio_int);
return -1;
}
static int bq24190_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 bq24190_platform_data *pdata = client->dev.platform_data;
struct bq24190_dev_info *bdi;
int ret;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(dev, "No support for SMBUS_BYTE_DATA\n");
return -ENODEV;
}
bdi = devm_kzalloc(dev, sizeof(*bdi), GFP_KERNEL);
if (!bdi) {
dev_err(dev, "Can't alloc bdi struct\n");
return -ENOMEM;
}
bdi->client = client;
bdi->dev = dev;
bdi->model = id->driver_data;
strncpy(bdi->model_name, id->name, I2C_NAME_SIZE);
mutex_init(&bdi->f_reg_lock);
bdi->first_time = true;
bdi->charger_health_valid = false;
bdi->battery_health_valid = false;
bdi->battery_status_valid = false;
i2c_set_clientdata(client, bdi);
if (dev->of_node)
ret = bq24190_setup_dt(bdi);
else
ret = bq24190_setup_pdata(bdi, pdata);
if (ret) {
dev_err(dev, "Can't get irq info\n");
return -EINVAL;
}
ret = devm_request_threaded_irq(dev, bdi->irq, NULL,
bq24190_irq_handler_thread,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"bq24190-charger", bdi);
if (ret < 0) {
dev_err(dev, "Can't set up irq handler\n");
goto out1;
}
pm_runtime_enable(dev);
pm_runtime_resume(dev);
ret = bq24190_hw_init(bdi);
if (ret < 0) {
dev_err(dev, "Hardware init failed\n");
goto out2;
}
bq24190_charger_init(&bdi->charger);
ret = power_supply_register(dev, &bdi->charger);
if (ret) {
dev_err(dev, "Can't register charger\n");
goto out2;
}
bq24190_battery_init(&bdi->battery);
ret = power_supply_register(dev, &bdi->battery);
if (ret) {
dev_err(dev, "Can't register battery\n");
goto out3;
}
ret = bq24190_sysfs_create_group(bdi);
if (ret) {
dev_err(dev, "Can't create sysfs entries\n");
goto out4;
}
return 0;
out4:
power_supply_unregister(&bdi->battery);
out3:
power_supply_unregister(&bdi->charger);
out2:
pm_runtime_disable(dev);
out1:
if (bdi->gpio_int)
gpio_free(bdi->gpio_int);
return ret;
}
static int bq24190_remove(struct i2c_client *client)
{
struct bq24190_dev_info *bdi = i2c_get_clientdata(client);
pm_runtime_get_sync(bdi->dev);
bq24190_register_reset(bdi);
pm_runtime_put_sync(bdi->dev);
bq24190_sysfs_remove_group(bdi);
power_supply_unregister(&bdi->battery);
power_supply_unregister(&bdi->charger);
pm_runtime_disable(bdi->dev);
if (bdi->gpio_int)
gpio_free(bdi->gpio_int);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int bq24190_pm_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct bq24190_dev_info *bdi = i2c_get_clientdata(client);
pm_runtime_get_sync(bdi->dev);
bq24190_register_reset(bdi);
pm_runtime_put_sync(bdi->dev);
return 0;
}
static int bq24190_pm_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct bq24190_dev_info *bdi = i2c_get_clientdata(client);
bdi->charger_health_valid = false;
bdi->battery_health_valid = false;
bdi->battery_status_valid = false;
pm_runtime_get_sync(bdi->dev);
bq24190_register_reset(bdi);
pm_runtime_put_sync(bdi->dev);
/* Things may have changed while suspended so alert upper layer */
power_supply_changed(&bdi->charger);
power_supply_changed(&bdi->battery);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(bq24190_pm_ops, bq24190_pm_suspend, bq24190_pm_resume);
/*
* Only support the bq24190 right now. The bq24192, bq24192i, and bq24193
* are similar but not identical so the driver needs to be extended to
* support them.
*/
static const struct i2c_device_id bq24190_i2c_ids[] = {
{ "bq24190", BQ24190_REG_VPRS_PN_24190 },
{ },
};
#ifdef CONFIG_OF
static const struct of_device_id bq24190_of_match[] = {
{ .compatible = "ti,bq24190", },
{ },
};
MODULE_DEVICE_TABLE(of, bq24190_of_match);
#else
static const struct of_device_id bq24190_of_match[] = {
{ },
};
#endif
static struct i2c_driver bq24190_driver = {
.probe = bq24190_probe,
.remove = bq24190_remove,
.id_table = bq24190_i2c_ids,
.driver = {
.name = "bq24190-charger",
.owner = THIS_MODULE,
.pm = &bq24190_pm_ops,
.of_match_table = of_match_ptr(bq24190_of_match),
},
};
module_i2c_driver(bq24190_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mark A. Greer <mgreer@animalcreek.com>");
MODULE_ALIAS("i2c:bq24190-charger");
MODULE_DESCRIPTION("TI BQ24190 Charger Driver");
......@@ -287,7 +287,7 @@ static struct gpio collie_batt_gpios[] = {
};
#ifdef CONFIG_PM
static int collie_bat_suspend(struct ucb1x00_dev *dev, pm_message_t state)
static int collie_bat_suspend(struct ucb1x00_dev *dev)
{
/* flush all pending status updates */
flush_work(&bat_work);
......
......@@ -458,6 +458,7 @@ max8925_power_dt_init(struct platform_device *pdev)
of_property_read_u32(np, "fast-charge", &fast_charge);
of_property_read_u32(np, "no-insert-detect", &no_insert_detect);
of_property_read_u32(np, "no-temp-support", &no_temp_support);
of_node_put(np);
pdata->batt_detect = batt_detect;
pdata->fast_charge = fast_charge;
......
......@@ -67,23 +67,42 @@ static int __power_supply_changed_work(struct device *dev, void *data)
static void power_supply_changed_work(struct work_struct *work)
{
unsigned long flags;
struct power_supply *psy = container_of(work, struct power_supply,
changed_work);
dev_dbg(psy->dev, "%s\n", __func__);
class_for_each_device(power_supply_class, NULL, psy,
__power_supply_changed_work);
power_supply_update_leds(psy);
kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE);
spin_lock_irqsave(&psy->changed_lock, flags);
if (psy->changed) {
psy->changed = false;
spin_unlock_irqrestore(&psy->changed_lock, flags);
class_for_each_device(power_supply_class, NULL, psy,
__power_supply_changed_work);
power_supply_update_leds(psy);
kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE);
spin_lock_irqsave(&psy->changed_lock, flags);
}
/*
* Dependent power supplies (e.g. battery) may have changed state
* as a result of this event, so poll again and hold the
* wakeup_source until all events are processed.
*/
if (!psy->changed)
pm_relax(psy->dev);
spin_unlock_irqrestore(&psy->changed_lock, flags);
}
void power_supply_changed(struct power_supply *psy)
{
unsigned long flags;
dev_dbg(psy->dev, "%s\n", __func__);
spin_lock_irqsave(&psy->changed_lock, flags);
psy->changed = true;
pm_stay_awake(psy->dev);
spin_unlock_irqrestore(&psy->changed_lock, flags);
schedule_work(&psy->changed_work);
}
EXPORT_SYMBOL_GPL(power_supply_changed);
......@@ -500,6 +519,11 @@ int power_supply_register(struct device *parent, struct power_supply *psy)
goto check_supplies_failed;
}
spin_lock_init(&psy->changed_lock);
rc = device_init_wakeup(dev, true);
if (rc)
goto wakeup_init_failed;
rc = kobject_set_name(&dev->kobj, "%s", psy->name);
if (rc)
goto kobject_set_name_failed;
......@@ -529,6 +553,7 @@ int power_supply_register(struct device *parent, struct power_supply *psy)
register_cooler_failed:
psy_unregister_thermal(psy);
register_thermal_failed:
wakeup_init_failed:
device_del(dev);
kobject_set_name_failed:
device_add_failed:
......@@ -546,6 +571,7 @@ void power_supply_unregister(struct power_supply *psy)
power_supply_remove_triggers(psy);
psy_unregister_cooler(psy);
psy_unregister_thermal(psy);
device_init_wakeup(psy->dev, false);
device_unregister(psy->dev);
}
EXPORT_SYMBOL_GPL(power_supply_unregister);
......
......@@ -118,7 +118,7 @@ static ssize_t power_supply_store_property(struct device *dev,
long long_val;
/* TODO: support other types than int */
ret = strict_strtol(buf, 10, &long_val);
ret = kstrtol(buf, 10, &long_val);
if (ret < 0)
return ret;
......
......@@ -14,6 +14,12 @@ config POWER_RESET_GPIO
If your board needs a GPIO high/low to power down, say Y and
create a binding in your devicetree.
config POWER_RESET_MSM
bool "Qualcomm MSM power-off driver"
depends on POWER_RESET && ARCH_MSM
help
Power off and restart support for Qualcomm boards.
config POWER_RESET_QNAP
bool "QNAP power-off driver"
depends on OF_GPIO && POWER_RESET && PLAT_ORION
......@@ -34,7 +40,14 @@ config POWER_RESET_RESTART
config POWER_RESET_VEXPRESS
bool "ARM Versatile Express power-off and reset driver"
depends on ARM || ARM64
depends on POWER_RESET
depends on POWER_RESET && VEXPRESS_CONFIG
help
Power off and reset support for the ARM Ltd. Versatile
Express boards.
config POWER_RESET_XGENE
bool "APM SoC X-Gene reset driver"
depends on ARM64
depends on POWER_RESET
help
Reboot support for the APM SoC X-Gene Eval boards.
obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o
obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o
obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o
obj-$(CONFIG_POWER_RESET_RESTART) += restart-poweroff.o
obj-$(CONFIG_POWER_RESET_VEXPRESS) += vexpress-poweroff.o
obj-$(CONFIG_POWER_RESET_XGENE) += xgene-reboot.o
/* Copyright (c) 2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only 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/delay.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/reboot.h>
#include <asm/system_misc.h>
static void __iomem *msm_ps_hold;
static void do_msm_restart(enum reboot_mode reboot_mode, const char *cmd)
{
writel(0, msm_ps_hold);
mdelay(10000);
}
static void do_msm_poweroff(void)
{
/* TODO: Add poweroff capability */
do_msm_restart(REBOOT_HARD, NULL);
}
static int msm_restart_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *mem;
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
msm_ps_hold = devm_ioremap_resource(dev, mem);
if (IS_ERR(msm_ps_hold))
return PTR_ERR(msm_ps_hold);
pm_power_off = do_msm_poweroff;
arm_pm_restart = do_msm_restart;
return 0;
}
static const struct of_device_id of_msm_restart_match[] = {
{ .compatible = "qcom,pshold", },
{},
};
MODULE_DEVICE_TABLE(of, of_msm_restart_match);
static struct platform_driver msm_restart_driver = {
.probe = msm_restart_probe,
.driver = {
.name = "msm-restart",
.of_match_table = of_match_ptr(of_msm_restart_match),
},
};
static int __init msm_restart_init(void)
{
return platform_driver_register(&msm_restart_driver);
}
device_initcall(msm_restart_init);
/*
* AppliedMicro X-Gene SoC Reboot Driver
*
* Copyright (c) 2013, Applied Micro Circuits Corporation
* Author: Feng Kan <fkan@apm.com>
* Author: Loc Ho <lho@apm.com>
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*
* This driver provides system reboot functionality for APM X-Gene SoC.
* For system shutdown, this is board specify. If a board designer
* implements GPIO shutdown, use the gpio-poweroff.c driver.
*/
#include <linux/io.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <asm/system_misc.h>
struct xgene_reboot_context {
struct platform_device *pdev;
void *csr;
u32 mask;
};
static struct xgene_reboot_context *xgene_restart_ctx;
static void xgene_restart(char str, const char *cmd)
{
struct xgene_reboot_context *ctx = xgene_restart_ctx;
unsigned long timeout;
/* Issue the reboot */
if (ctx)
writel(ctx->mask, ctx->csr);
timeout = jiffies + HZ;
while (time_before(jiffies, timeout))
cpu_relax();
dev_emerg(&ctx->pdev->dev, "Unable to restart system\n");
}
static int xgene_reboot_probe(struct platform_device *pdev)
{
struct xgene_reboot_context *ctx;
ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx) {
dev_err(&pdev->dev, "out of memory for context\n");
return -ENODEV;
}
ctx->csr = of_iomap(pdev->dev.of_node, 0);
if (!ctx->csr) {
devm_kfree(&pdev->dev, ctx);
dev_err(&pdev->dev, "can not map resource\n");
return -ENODEV;
}
if (of_property_read_u32(pdev->dev.of_node, "mask", &ctx->mask))
ctx->mask = 0xFFFFFFFF;
ctx->pdev = pdev;
arm_pm_restart = xgene_restart;
xgene_restart_ctx = ctx;
return 0;
}
static struct of_device_id xgene_reboot_of_match[] = {
{ .compatible = "apm,xgene-reboot" },
{}
};
static struct platform_driver xgene_reboot_driver = {
.probe = xgene_reboot_probe,
.driver = {
.name = "xgene-reboot",
.of_match_table = xgene_reboot_of_match,
},
};
static int __init xgene_reboot_init(void)
{
return platform_driver_register(&xgene_reboot_driver);
}
device_initcall(xgene_reboot_init);
......@@ -25,6 +25,10 @@
#include <linux/slab.h>
#include <linux/i2c/twl4030-madc.h>
/* RX51 specific channels */
#define TWL4030_MADC_BTEMP_RX51 TWL4030_MADC_ADCIN0
#define TWL4030_MADC_BCI_RX51 TWL4030_MADC_ADCIN4
struct rx51_device_info {
struct device *dev;
struct power_supply bat;
......@@ -37,7 +41,7 @@ static int rx51_battery_read_adc(int channel)
{
struct twl4030_madc_request req;
req.channels = 1 << channel;
req.channels = channel;
req.do_avg = 1;
req.method = TWL4030_MADC_SW1;
req.func_cb = NULL;
......@@ -47,7 +51,7 @@ static int rx51_battery_read_adc(int channel)
if (twl4030_madc_conversion(&req) <= 0)
return -ENODATA;
return req.rbuf[channel];
return req.rbuf[ffs(channel) - 1];
}
/*
......@@ -56,7 +60,7 @@ static int rx51_battery_read_adc(int channel)
*/
static int rx51_battery_read_voltage(struct rx51_device_info *di)
{
int voltage = rx51_battery_read_adc(12);
int voltage = rx51_battery_read_adc(TWL4030_MADC_VBAT);
if (voltage < 0)
return voltage;
......@@ -108,7 +112,7 @@ static int rx51_battery_read_temperature(struct rx51_device_info *di)
{
int min = 0;
int max = ARRAY_SIZE(rx51_temp_table2) - 1;
int raw = rx51_battery_read_adc(0);
int raw = rx51_battery_read_adc(TWL4030_MADC_BTEMP_RX51);
/* Zero and negative values are undefined */
if (raw <= 0)
......@@ -142,7 +146,7 @@ static int rx51_battery_read_temperature(struct rx51_device_info *di)
*/
static int rx51_battery_read_capacity(struct rx51_device_info *di)
{
int capacity = rx51_battery_read_adc(4);
int capacity = rx51_battery_read_adc(TWL4030_MADC_BCI_RX51);
if (capacity < 0)
return capacity;
......
......@@ -150,7 +150,7 @@ static void tosa_bat_external_power_changed(struct power_supply *psy)
static irqreturn_t tosa_bat_gpio_isr(int irq, void *data)
{
pr_info("tosa_bat_gpio irq: %d\n", gpio_get_value(irq_to_gpio(irq)));
pr_info("tosa_bat_gpio irq\n");
schedule_work(&bat_work);
return IRQ_HANDLED;
}
......
......@@ -189,7 +189,12 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
/* Need to keep regulator on */
if (!bci->usb_enabled) {
regulator_enable(bci->usb_reg);
ret = regulator_enable(bci->usb_reg);
if (ret) {
dev_err(bci->dev,
"Failed to enable regulator\n");
return ret;
}
bci->usb_enabled = 1;
}
......
/*
* Dumb driver for LiIon batteries using TWL4030 madc.
*
* Copyright 2013 Golden Delicious Computers
* Lukas Märdian <lukas@goldelico.com>
*
* Based on dumb driver for gta01 battery
* Copyright 2009 Openmoko, Inc
* Balaji Rao <balajirrao@openmoko.org>
*/
#include <linux/module.h>
#include <linux/param.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/slab.h>
#include <linux/sort.h>
#include <linux/i2c/twl4030-madc.h>
#include <linux/power/twl4030_madc_battery.h>
struct twl4030_madc_battery {
struct power_supply psy;
struct twl4030_madc_bat_platform_data *pdata;
};
static enum power_supply_property twl4030_madc_bat_props[] = {
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
};
static int madc_read(int index)
{
struct twl4030_madc_request req;
int val;
req.channels = index;
req.method = TWL4030_MADC_SW2;
req.type = TWL4030_MADC_WAIT;
req.do_avg = 0;
req.raw = false;
req.func_cb = NULL;
val = twl4030_madc_conversion(&req);
if (val < 0)
return val;
return req.rbuf[ffs(index) - 1];
}
static int twl4030_madc_bat_get_charging_status(void)
{
return (madc_read(TWL4030_MADC_ICHG) > 0) ? 1 : 0;
}
static int twl4030_madc_bat_get_voltage(void)
{
return madc_read(TWL4030_MADC_VBAT);
}
static int twl4030_madc_bat_get_current(void)
{
return madc_read(TWL4030_MADC_ICHG) * 1000;
}
static int twl4030_madc_bat_get_temp(void)
{
return madc_read(TWL4030_MADC_BTEMP) * 10;
}
static int twl4030_madc_bat_voltscale(struct twl4030_madc_battery *bat,
int volt)
{
struct twl4030_madc_bat_calibration *calibration;
int i, res = 0;
/* choose charging curve */
if (twl4030_madc_bat_get_charging_status())
calibration = bat->pdata->charging;
else
calibration = bat->pdata->discharging;
if (volt > calibration[0].voltage) {
res = calibration[0].level;
} else {
for (i = 0; calibration[i+1].voltage >= 0; i++) {
if (volt <= calibration[i].voltage &&
volt >= calibration[i+1].voltage) {
/* interval found - interpolate within range */
res = calibration[i].level -
((calibration[i].voltage - volt) *
(calibration[i].level -
calibration[i+1].level)) /
(calibration[i].voltage -
calibration[i+1].voltage);
break;
}
}
}
return res;
}
static int twl4030_madc_bat_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct twl4030_madc_battery *bat = container_of(psy,
struct twl4030_madc_battery, psy);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
if (twl4030_madc_bat_voltscale(bat,
twl4030_madc_bat_get_voltage()) > 95)
val->intval = POWER_SUPPLY_STATUS_FULL;
else {
if (twl4030_madc_bat_get_charging_status())
val->intval = POWER_SUPPLY_STATUS_CHARGING;
else
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
}
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = twl4030_madc_bat_get_voltage() * 1000;
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
val->intval = twl4030_madc_bat_get_current();
break;
case POWER_SUPPLY_PROP_PRESENT:
/* assume battery is always present */
val->intval = 1;
break;
case POWER_SUPPLY_PROP_CHARGE_NOW: {
int percent = twl4030_madc_bat_voltscale(bat,
twl4030_madc_bat_get_voltage());
val->intval = (percent * bat->pdata->capacity) / 100;
break;
}
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = twl4030_madc_bat_voltscale(bat,
twl4030_madc_bat_get_voltage());
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
val->intval = bat->pdata->capacity;
break;
case POWER_SUPPLY_PROP_TEMP:
val->intval = twl4030_madc_bat_get_temp();
break;
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: {
int percent = twl4030_madc_bat_voltscale(bat,
twl4030_madc_bat_get_voltage());
/* in mAh */
int chg = (percent * (bat->pdata->capacity/1000))/100;
/* assume discharge with 400 mA (ca. 1.5W) */
val->intval = (3600l * chg) / 400;
break;
}
default:
return -EINVAL;
}
return 0;
}
static void twl4030_madc_bat_ext_changed(struct power_supply *psy)
{
struct twl4030_madc_battery *bat = container_of(psy,
struct twl4030_madc_battery, psy);
power_supply_changed(&bat->psy);
}
static int twl4030_cmp(const void *a, const void *b)
{
return ((struct twl4030_madc_bat_calibration *)b)->voltage -
((struct twl4030_madc_bat_calibration *)a)->voltage;
}
static int twl4030_madc_battery_probe(struct platform_device *pdev)
{
struct twl4030_madc_battery *twl4030_madc_bat;
struct twl4030_madc_bat_platform_data *pdata = pdev->dev.platform_data;
twl4030_madc_bat = kzalloc(sizeof(*twl4030_madc_bat), GFP_KERNEL);
if (!twl4030_madc_bat)
return -ENOMEM;
twl4030_madc_bat->psy.name = "twl4030_battery";
twl4030_madc_bat->psy.type = POWER_SUPPLY_TYPE_BATTERY;
twl4030_madc_bat->psy.properties = twl4030_madc_bat_props;
twl4030_madc_bat->psy.num_properties =
ARRAY_SIZE(twl4030_madc_bat_props);
twl4030_madc_bat->psy.get_property = twl4030_madc_bat_get_property;
twl4030_madc_bat->psy.external_power_changed =
twl4030_madc_bat_ext_changed;
/* sort charging and discharging calibration data */
sort(pdata->charging, pdata->charging_size,
sizeof(struct twl4030_madc_bat_calibration),
twl4030_cmp, NULL);
sort(pdata->discharging, pdata->discharging_size,
sizeof(struct twl4030_madc_bat_calibration),
twl4030_cmp, NULL);
twl4030_madc_bat->pdata = pdata;
platform_set_drvdata(pdev, twl4030_madc_bat);
power_supply_register(&pdev->dev, &twl4030_madc_bat->psy);
return 0;
}
static int twl4030_madc_battery_remove(struct platform_device *pdev)
{
struct twl4030_madc_battery *bat = platform_get_drvdata(pdev);
power_supply_unregister(&bat->psy);
kfree(bat);
return 0;
}
static struct platform_driver twl4030_madc_battery_driver = {
.driver = {
.name = "twl4030_madc_battery",
},
.probe = twl4030_madc_battery_probe,
.remove = twl4030_madc_battery_remove,
};
module_platform_driver(twl4030_madc_battery_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Lukas Märdian <lukas@goldelico.com>");
MODULE_DESCRIPTION("twl4030_madc battery driver");
/*
* Platform data for the TI bq24190 battery charger driver.
*
* 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.
*/
#ifndef _BQ24190_CHARGER_H_
#define _BQ24190_CHARGER_H_
struct bq24190_platform_data {
unsigned int gpio_int; /* GPIO pin that's connected to INT# */
};
#endif
/*
* Dumb driver for LiIon batteries using TWL4030 madc.
*
* Copyright 2013 Golden Delicious Computers
* Nikolaus Schaller <hns@goldelico.com>
*
* 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#ifndef __TWL4030_MADC_BATTERY_H
#define __TWL4030_MADC_BATTERY_H
/*
* Usually we can assume 100% @ 4.15V and 0% @ 3.3V but curves differ for
* charging and discharging!
*/
struct twl4030_madc_bat_calibration {
short voltage; /* in mV - specify -1 for end of list */
short level; /* in percent (0 .. 100%) */
};
struct twl4030_madc_bat_platform_data {
unsigned int capacity; /* total capacity in uAh */
struct twl4030_madc_bat_calibration *charging;
int charging_size;
struct twl4030_madc_bat_calibration *discharging;
int discharging_size;
};
#endif
......@@ -15,6 +15,7 @@
#include <linux/workqueue.h>
#include <linux/leds.h>
#include <linux/spinlock.h>
struct device;
......@@ -194,6 +195,8 @@ struct power_supply {
/* private */
struct device *dev;
struct work_struct changed_work;
spinlock_t changed_lock;
bool changed;
#ifdef CONFIG_THERMAL
struct thermal_zone_device *tzd;
struct thermal_cooling_device *tcd;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册