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

Pull power supply and reset updates from Sebastian Reichel:
 "New drivers:
   - Linear ltc3651 charger driver
   - Motorola CPCAP battery fuel-gauge driver

  New chip/feature support:
   - bq27xxx: prepare for chip data setup
   - axp20x_battery: support max charge current setup

  New core features:
   - add Apple Brick ID type
   - support "supplied-from" device property for generic ACPI/pdata support
   - support strings for sysfs properties representing enums
   - introduce battery-info (backend is DT only for now)
   - provide reboot-mode header globally

  .. and misc fixes"

* tag 'for-v4.13' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (39 commits)
  power: supply: sbs-battery: Don't needlessly set CAPACITY_MODE
  power: supply: sbs-battery: Prevent CAPACITY_MODE races
  power: supply: bq24735: remove incorrect le16_to_cpu calls
  power: supply: sbs-battery: remove incorrect le16_to_cpu calls
  power: supply: cpcap-charger: Add missing power_supply_config
  power: supply: twl4030-charger: move allocation of iio channel to the beginning
  power: supply: twl4030-charger: allocate iio by devm_iio_channel_get() and fix error path
  power: supply: core: constify psy_tcd_ops.
  dt-bindings: power: supply: cpcap-battery: Add power-supplies property
  dt-bindings: power: supply: move max8903-charger.txt to proper location
  dt-bindings: power: supply: move maxim,max14656.txt to proper location
  power: supply: twl4030_charger: Use sysfs_match_string() helper
  power: reset: reboot-mode: Make include file global
  power: supply: axp20x_battery: add DT support for battery max constant charge current
  power: supply: axp20x_battery: add support for DT battery
  power: supply: bq27xxx: Add power_supply_battery_info support
  power: supply: bq27xxx: Add chip data memory read/write support
  power: supply: bq27xxx: Add bulk transfer bus methods
  dt-bindings: power: supply: bq27xxx: Add monitored-battery documentation
  power: supply: core: Add power_supply_prop_precharge
  ...
This commit is contained in:
Linus Torvalds
2017-07-04 14:25:14 -07:00
31 changed files with 2204 additions and 233 deletions

View File

@@ -58,9 +58,9 @@ config POWER_RESET_BRCMKONA
config POWER_RESET_BRCMSTB
bool "Broadcom STB reset driver"
depends on ARM || MIPS || COMPILE_TEST
depends on ARM || ARM64 || MIPS || COMPILE_TEST
depends on MFD_SYSCON
default ARCH_BRCMSTB
default ARCH_BRCMSTB || BMIPS_GENERIC
help
This driver provides restart support for Broadcom STB boards.

View File

@@ -97,7 +97,7 @@ static void at91_lpddr_poweroff(void)
"r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF),
"r" (at91_shdwc_base),
"r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW)
: "r0");
: "r6");
}
static int at91_poweroff_get_wakeup_mode(struct device_node *np)

View File

@@ -132,7 +132,7 @@ static void at91_lpddr_poweroff(void)
"r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF),
"r" (at91_shdwc->at91_shdwc_base),
"r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW)
: "r0");
: "r6");
}
static u32 at91_shdwc_debouncer_value(struct platform_device *pdev,

View File

@@ -13,7 +13,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/reboot.h>
#include "reboot-mode.h"
#include <linux/reboot-mode.h>
#define PREFIX "mode-"

View File

@@ -1,18 +0,0 @@
#ifndef __REBOOT_MODE_H__
#define __REBOOT_MODE_H__
struct reboot_mode_driver {
struct device *dev;
struct list_head head;
int (*write)(struct reboot_mode_driver *reboot, unsigned int magic);
struct notifier_block reboot_notifier;
};
int reboot_mode_register(struct reboot_mode_driver *reboot);
int reboot_mode_unregister(struct reboot_mode_driver *reboot);
int devm_reboot_mode_register(struct device *dev,
struct reboot_mode_driver *reboot);
void devm_reboot_mode_unregister(struct device *dev,
struct reboot_mode_driver *reboot);
#endif

View File

@@ -15,7 +15,7 @@
#include <linux/reboot.h>
#include <linux/regmap.h>
#include <linux/mfd/syscon.h>
#include "reboot-mode.h"
#include <linux/reboot-mode.h>
struct syscon_reboot_mode {
struct regmap *map;

View File

@@ -82,6 +82,14 @@ config BATTERY_ACT8945A
Say Y here to enable support for power supply provided by
Active-semi ActivePath ACT8945A charger.
config BATTERY_CPCAP
tristate "Motorola CPCAP PMIC battery driver"
depends on MFD_CPCAP && IIO
default MFD_CPCAP
help
Say Y here to enable support for battery on Motorola
phones and tablets such as droid 4.
config BATTERY_DS2760
tristate "DS2760 battery driver (HP iPAQ & others)"
depends on W1 && W1_SLAVE_DS2760
@@ -190,6 +198,17 @@ config BATTERY_BQ27XXX_I2C
Say Y here to enable support for batteries with BQ27xxx chips
connected over an I2C bus.
config BATTERY_BQ27XXX_DT_UPDATES_NVM
bool "BQ27xxx support for update of NVM/flash data memory"
depends on BATTERY_BQ27XXX_I2C
help
Say Y here to enable devicetree monitored-battery config to update
NVM/flash data memory. Only enable this option for devices with a
fuel gauge mounted on the circuit board, and a battery that cannot
easily be replaced with one of a different type. Not for
general-purpose kernels, as this can cause misconfiguration of a
smart battery with embedded NVM/flash.
config BATTERY_DA9030
tristate "DA9030 battery driver"
depends on PMIC_DA903X
@@ -408,6 +427,13 @@ config CHARGER_MANAGER
runtime and in suspend-to-RAM by waking up the system periodically
with help of suspend_again support.
config CHARGER_LTC3651
tristate "LTC3651 charger"
depends on GPIOLIB
help
Say Y to include support for the LTC3651 battery charger which reports
its status via GPIO lines.
config CHARGER_MAX14577
tristate "Maxim MAX14577/77836 battery charger driver"
depends on MFD_MAX14577

View File

@@ -20,6 +20,7 @@ obj-$(CONFIG_BATTERY_88PM860X) += 88pm860x_battery.o
obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o
obj-$(CONFIG_BATTERY_AXP20X) += axp20x_battery.o
obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o
obj-$(CONFIG_BATTERY_CPCAP) += cpcap-battery.o
obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o
obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o
obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o
@@ -61,6 +62,7 @@ obj-$(CONFIG_CHARGER_LP8727) += lp8727_charger.o
obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o
obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o
obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o
obj-$(CONFIG_CHARGER_LTC3651) += ltc3651-charger.o
obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o
obj-$(CONFIG_CHARGER_DETECTOR_MAX14656) += max14656_charger_detector.o
obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o

View File

@@ -60,6 +60,8 @@ struct axp20x_batt_ps {
struct iio_channel *batt_chrg_i;
struct iio_channel *batt_dischrg_i;
struct iio_channel *batt_v;
/* Maximum constant charge current */
unsigned int max_ccc;
u8 axp_id;
};
@@ -129,6 +131,14 @@ static void raw_to_constant_charge_current(struct axp20x_batt_ps *axp, int *val)
*val = *val * 150000 + 300000;
}
static void constant_charge_current_to_raw(struct axp20x_batt_ps *axp, int *val)
{
if (axp->axp_id == AXP209_ID)
*val = (*val - 300000) / 100000;
else
*val = (*val - 300000) / 150000;
}
static int axp20x_get_constant_charge_current(struct axp20x_batt_ps *axp,
int *val)
{
@@ -221,9 +231,7 @@ static int axp20x_battery_get_prop(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
val->intval = AXP20X_CHRG_CTRL1_TGT_CURR;
raw_to_constant_charge_current(axp20x_batt, &val->intval);
val->intval = axp20x_batt->max_ccc;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
@@ -340,10 +348,10 @@ static int axp20x_battery_set_max_voltage(struct axp20x_batt_ps *axp20x_batt,
static int axp20x_set_constant_charge_current(struct axp20x_batt_ps *axp_batt,
int charge_current)
{
if (axp_batt->axp_id == AXP209_ID)
charge_current = (charge_current - 300000) / 100000;
else
charge_current = (charge_current - 300000) / 150000;
if (charge_current > axp_batt->max_ccc)
return -EINVAL;
constant_charge_current_to_raw(axp_batt, &charge_current);
if (charge_current > AXP20X_CHRG_CTRL1_TGT_CURR || charge_current < 0)
return -EINVAL;
@@ -352,6 +360,36 @@ static int axp20x_set_constant_charge_current(struct axp20x_batt_ps *axp_batt,
AXP20X_CHRG_CTRL1_TGT_CURR, charge_current);
}
static int axp20x_set_max_constant_charge_current(struct axp20x_batt_ps *axp,
int charge_current)
{
bool lower_max = false;
constant_charge_current_to_raw(axp, &charge_current);
if (charge_current > AXP20X_CHRG_CTRL1_TGT_CURR || charge_current < 0)
return -EINVAL;
raw_to_constant_charge_current(axp, &charge_current);
if (charge_current > axp->max_ccc)
dev_warn(axp->dev,
"Setting max constant charge current higher than previously defined. Note that increasing the constant charge current may damage your battery.\n");
else
lower_max = true;
axp->max_ccc = charge_current;
if (lower_max) {
int current_cc;
axp20x_get_constant_charge_current(axp, &current_cc);
if (current_cc > charge_current)
axp20x_set_constant_charge_current(axp, charge_current);
}
return 0;
}
static int axp20x_set_voltage_min_design(struct axp20x_batt_ps *axp_batt,
int min_voltage)
{
@@ -380,6 +418,9 @@ static int axp20x_battery_set_prop(struct power_supply *psy,
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
return axp20x_set_constant_charge_current(axp20x_batt,
val->intval);
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
return axp20x_set_max_constant_charge_current(axp20x_batt,
val->intval);
default:
return -EINVAL;
@@ -405,7 +446,8 @@ static int axp20x_battery_prop_writeable(struct power_supply *psy,
{
return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN ||
psp == POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN ||
psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT;
psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT ||
psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX;
}
static const struct power_supply_desc axp20x_batt_ps_desc = {
@@ -433,6 +475,7 @@ static int axp20x_power_probe(struct platform_device *pdev)
{
struct axp20x_batt_ps *axp20x_batt;
struct power_supply_config psy_cfg = {};
struct power_supply_battery_info info;
if (!of_device_is_available(pdev->dev.of_node))
return -ENODEV;
@@ -484,6 +527,35 @@ static int axp20x_power_probe(struct platform_device *pdev)
return PTR_ERR(axp20x_batt->batt);
}
if (!power_supply_get_battery_info(axp20x_batt->batt, &info)) {
int vmin = info.voltage_min_design_uv;
int ccc = info.constant_charge_current_max_ua;
if (vmin > 0 && axp20x_set_voltage_min_design(axp20x_batt,
vmin))
dev_err(&pdev->dev,
"couldn't set voltage_min_design\n");
/* Set max to unverified value to be able to set CCC */
axp20x_batt->max_ccc = ccc;
if (ccc <= 0 || axp20x_set_constant_charge_current(axp20x_batt,
ccc)) {
dev_err(&pdev->dev,
"couldn't set constant charge current from DT: fallback to minimum value\n");
ccc = 300000;
axp20x_batt->max_ccc = ccc;
axp20x_set_constant_charge_current(axp20x_batt, ccc);
}
}
/*
* Update max CCC to a valid value if battery info is present or set it
* to current register value by default.
*/
axp20x_get_constant_charge_current(axp20x_batt,
&axp20x_batt->max_ccc);
return 0;
}

View File

@@ -339,7 +339,7 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
"VBUS_REMOVAL", "VBUS_VALID", "VBUS_NOT_VALID", NULL };
static const char * const axp22x_irq_names[] = {
"VBUS_PLUGIN", "VBUS_REMOVAL", NULL };
static const char * const *irq_names;
const char * const *irq_names;
const struct power_supply_desc *usb_power_desc;
int i, irq, ret;

View File

@@ -81,14 +81,12 @@ static int bq24735_charger_property_is_writeable(struct power_supply *psy,
static inline int bq24735_write_word(struct i2c_client *client, u8 reg,
u16 value)
{
return i2c_smbus_write_word_data(client, reg, le16_to_cpu(value));
return i2c_smbus_write_word_data(client, reg, value);
}
static inline int bq24735_read_word(struct i2c_client *client, u8 reg)
{
s32 ret = i2c_smbus_read_word_data(client, reg);
return ret < 0 ? ret : le16_to_cpu(ret);
return i2c_smbus_read_word_data(client, reg);
}
static int bq24735_update_word(struct i2c_client *client, u8 reg,

View File

@@ -5,6 +5,7 @@
* Copyright (C) 2008 Eurotech S.p.A. <info@eurotech.it>
* Copyright (C) 2010-2011 Lars-Peter Clausen <lars@metafoo.de>
* Copyright (C) 2011 Pali Rohár <pali.rohar@gmail.com>
* Copyright (C) 2017 Liam Breck <kernel@networkimprov.net>
*
* Based on a previous work by Copyright (C) 2008 Texas Instruments, Inc.
*
@@ -65,6 +66,7 @@
#define BQ27XXX_FLAG_DSC BIT(0)
#define BQ27XXX_FLAG_SOCF BIT(1) /* State-of-Charge threshold final */
#define BQ27XXX_FLAG_SOC1 BIT(2) /* State-of-Charge threshold 1 */
#define BQ27XXX_FLAG_CFGUP BIT(4)
#define BQ27XXX_FLAG_FC BIT(9)
#define BQ27XXX_FLAG_OTD BIT(14)
#define BQ27XXX_FLAG_OTC BIT(15)
@@ -78,6 +80,12 @@
#define BQ27000_FLAG_FC BIT(5)
#define BQ27000_FLAG_CHGS BIT(7) /* Charge state flag */
/* control register params */
#define BQ27XXX_SEALED 0x20
#define BQ27XXX_SET_CFGUPDATE 0x13
#define BQ27XXX_SOFT_RESET 0x42
#define BQ27XXX_RESET 0x41
#define BQ27XXX_RS (20) /* Resistor sense mOhm */
#define BQ27XXX_POWER_CONSTANT (29200) /* 29.2 µV^2 * 1000 */
#define BQ27XXX_CURRENT_CONSTANT (3570) /* 3.57 µV * 1000 */
@@ -108,9 +116,21 @@ enum bq27xxx_reg_index {
BQ27XXX_REG_SOC, /* State-of-Charge */
BQ27XXX_REG_DCAP, /* Design Capacity */
BQ27XXX_REG_AP, /* Average Power */
BQ27XXX_DM_CTRL, /* Block Data Control */
BQ27XXX_DM_CLASS, /* Data Class */
BQ27XXX_DM_BLOCK, /* Data Block */
BQ27XXX_DM_DATA, /* Block Data */
BQ27XXX_DM_CKSUM, /* Block Data Checksum */
BQ27XXX_REG_MAX, /* sentinel */
};
#define BQ27XXX_DM_REG_ROWS \
[BQ27XXX_DM_CTRL] = 0x61, \
[BQ27XXX_DM_CLASS] = 0x3e, \
[BQ27XXX_DM_BLOCK] = 0x3f, \
[BQ27XXX_DM_DATA] = 0x40, \
[BQ27XXX_DM_CKSUM] = 0x60
/* Register mappings */
static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27000] = {
@@ -131,6 +151,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x0b,
[BQ27XXX_REG_DCAP] = 0x76,
[BQ27XXX_REG_AP] = 0x24,
[BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
[BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
[BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
[BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
[BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
},
[BQ27010] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -150,6 +175,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x0b,
[BQ27XXX_REG_DCAP] = 0x76,
[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
[BQ27XXX_DM_CTRL] = INVALID_REG_ADDR,
[BQ27XXX_DM_CLASS] = INVALID_REG_ADDR,
[BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR,
[BQ27XXX_DM_DATA] = INVALID_REG_ADDR,
[BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR,
},
[BQ2750X] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -169,6 +199,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
BQ27XXX_DM_REG_ROWS,
},
[BQ2751X] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -188,6 +219,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x20,
[BQ27XXX_REG_DCAP] = 0x2e,
[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
BQ27XXX_DM_REG_ROWS,
},
[BQ27500] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -207,6 +239,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x24,
BQ27XXX_DM_REG_ROWS,
},
[BQ27510G1] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -226,6 +259,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x24,
BQ27XXX_DM_REG_ROWS,
},
[BQ27510G2] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -245,6 +279,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x24,
BQ27XXX_DM_REG_ROWS,
},
[BQ27510G3] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -264,6 +299,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x20,
[BQ27XXX_REG_DCAP] = 0x2e,
[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
BQ27XXX_DM_REG_ROWS,
},
[BQ27520G1] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -283,6 +319,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x24,
BQ27XXX_DM_REG_ROWS,
},
[BQ27520G2] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -302,6 +339,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x24,
BQ27XXX_DM_REG_ROWS,
},
[BQ27520G3] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -321,6 +359,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x24,
BQ27XXX_DM_REG_ROWS,
},
[BQ27520G4] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -340,6 +379,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x20,
[BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
BQ27XXX_DM_REG_ROWS,
},
[BQ27530] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -359,6 +399,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
[BQ27XXX_REG_AP] = 0x24,
BQ27XXX_DM_REG_ROWS,
},
[BQ27541] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -378,6 +419,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x24,
BQ27XXX_DM_REG_ROWS,
},
[BQ27545] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -397,6 +439,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
[BQ27XXX_REG_AP] = 0x24,
BQ27XXX_DM_REG_ROWS,
},
[BQ27421] = {
[BQ27XXX_REG_CTRL] = 0x00,
@@ -416,6 +459,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_SOC] = 0x1c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x18,
BQ27XXX_DM_REG_ROWS,
},
};
@@ -757,6 +801,73 @@ static struct {
static DEFINE_MUTEX(bq27xxx_list_lock);
static LIST_HEAD(bq27xxx_battery_devices);
#define BQ27XXX_MSLEEP(i) usleep_range((i)*1000, (i)*1000+500)
#define BQ27XXX_DM_SZ 32
struct bq27xxx_dm_reg {
u8 subclass_id;
u8 offset;
u8 bytes;
u16 min, max;
};
/**
* struct bq27xxx_dm_buf - chip data memory buffer
* @class: data memory subclass_id
* @block: data memory block number
* @data: data from/for the block
* @has_data: true if data has been filled by read
* @dirty: true if data has changed since last read/write
*
* Encapsulates info required to manage chip data memory blocks.
*/
struct bq27xxx_dm_buf {
u8 class;
u8 block;
u8 data[BQ27XXX_DM_SZ];
bool has_data, dirty;
};
#define BQ27XXX_DM_BUF(di, i) { \
.class = (di)->dm_regs[i].subclass_id, \
.block = (di)->dm_regs[i].offset / BQ27XXX_DM_SZ, \
}
static inline u16 *bq27xxx_dm_reg_ptr(struct bq27xxx_dm_buf *buf,
struct bq27xxx_dm_reg *reg)
{
if (buf->class == reg->subclass_id &&
buf->block == reg->offset / BQ27XXX_DM_SZ)
return (u16 *) (buf->data + reg->offset % BQ27XXX_DM_SZ);
return NULL;
}
enum bq27xxx_dm_reg_id {
BQ27XXX_DM_DESIGN_CAPACITY = 0,
BQ27XXX_DM_DESIGN_ENERGY,
BQ27XXX_DM_TERMINATE_VOLTAGE,
};
static const char * const bq27xxx_dm_reg_name[] = {
[BQ27XXX_DM_DESIGN_CAPACITY] = "design-capacity",
[BQ27XXX_DM_DESIGN_ENERGY] = "design-energy",
[BQ27XXX_DM_TERMINATE_VOLTAGE] = "terminate-voltage",
};
static bool bq27xxx_dt_to_nvm = true;
module_param_named(dt_monitored_battery_updates_nvm, bq27xxx_dt_to_nvm, bool, 0444);
MODULE_PARM_DESC(dt_monitored_battery_updates_nvm,
"Devicetree monitored-battery config updates data memory on NVM/flash chips.\n"
"Users must set this =0 when installing a different type of battery!\n"
"Default is =1."
#ifndef CONFIG_BATTERY_BQ27XXX_DT_UPDATES_NVM
"\nSetting this affects future kernel updates, not the current configuration."
#endif
);
static int poll_interval_param_set(const char *val, const struct kernel_param *kp)
{
struct bq27xxx_device_info *di;
@@ -794,11 +905,419 @@ MODULE_PARM_DESC(poll_interval,
static inline int bq27xxx_read(struct bq27xxx_device_info *di, int reg_index,
bool single)
{
/* Reports EINVAL for invalid/missing registers */
int ret;
if (!di || di->regs[reg_index] == INVALID_REG_ADDR)
return -EINVAL;
return di->bus.read(di, di->regs[reg_index], single);
ret = di->bus.read(di, di->regs[reg_index], single);
if (ret < 0)
dev_dbg(di->dev, "failed to read register 0x%02x (index %d)\n",
di->regs[reg_index], reg_index);
return ret;
}
static inline int bq27xxx_write(struct bq27xxx_device_info *di, int reg_index,
u16 value, bool single)
{
int ret;
if (!di || di->regs[reg_index] == INVALID_REG_ADDR)
return -EINVAL;
if (!di->bus.write)
return -EPERM;
ret = di->bus.write(di, di->regs[reg_index], value, single);
if (ret < 0)
dev_dbg(di->dev, "failed to write register 0x%02x (index %d)\n",
di->regs[reg_index], reg_index);
return ret;
}
static inline int bq27xxx_read_block(struct bq27xxx_device_info *di, int reg_index,
u8 *data, int len)
{
int ret;
if (!di || di->regs[reg_index] == INVALID_REG_ADDR)
return -EINVAL;
if (!di->bus.read_bulk)
return -EPERM;
ret = di->bus.read_bulk(di, di->regs[reg_index], data, len);
if (ret < 0)
dev_dbg(di->dev, "failed to read_bulk register 0x%02x (index %d)\n",
di->regs[reg_index], reg_index);
return ret;
}
static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_index,
u8 *data, int len)
{
int ret;
if (!di || di->regs[reg_index] == INVALID_REG_ADDR)
return -EINVAL;
if (!di->bus.write_bulk)
return -EPERM;
ret = di->bus.write_bulk(di, di->regs[reg_index], data, len);
if (ret < 0)
dev_dbg(di->dev, "failed to write_bulk register 0x%02x (index %d)\n",
di->regs[reg_index], reg_index);
return ret;
}
static int bq27xxx_battery_seal(struct bq27xxx_device_info *di)
{
int ret;
ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, BQ27XXX_SEALED, false);
if (ret < 0) {
dev_err(di->dev, "bus error on seal: %d\n", ret);
return ret;
}
return 0;
}
static int bq27xxx_battery_unseal(struct bq27xxx_device_info *di)
{
int ret;
if (di->unseal_key == 0) {
dev_err(di->dev, "unseal failed due to missing key\n");
return -EINVAL;
}
ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)(di->unseal_key >> 16), false);
if (ret < 0)
goto out;
ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)di->unseal_key, false);
if (ret < 0)
goto out;
return 0;
out:
dev_err(di->dev, "bus error on unseal: %d\n", ret);
return ret;
}
static u8 bq27xxx_battery_checksum_dm_block(struct bq27xxx_dm_buf *buf)
{
u16 sum = 0;
int i;
for (i = 0; i < BQ27XXX_DM_SZ; i++)
sum += buf->data[i];
sum &= 0xff;
return 0xff - sum;
}
static int bq27xxx_battery_read_dm_block(struct bq27xxx_device_info *di,
struct bq27xxx_dm_buf *buf)
{
int ret;
buf->has_data = false;
ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true);
if (ret < 0)
goto out;
ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true);
if (ret < 0)
goto out;
BQ27XXX_MSLEEP(1);
ret = bq27xxx_read_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ);
if (ret < 0)
goto out;
ret = bq27xxx_read(di, BQ27XXX_DM_CKSUM, true);
if (ret < 0)
goto out;
if ((u8)ret != bq27xxx_battery_checksum_dm_block(buf)) {
ret = -EINVAL;
goto out;
}
buf->has_data = true;
buf->dirty = false;
return 0;
out:
dev_err(di->dev, "bus error reading chip memory: %d\n", ret);
return ret;
}
static void bq27xxx_battery_update_dm_block(struct bq27xxx_device_info *di,
struct bq27xxx_dm_buf *buf,
enum bq27xxx_dm_reg_id reg_id,
unsigned int val)
{
struct bq27xxx_dm_reg *reg = &di->dm_regs[reg_id];
const char *str = bq27xxx_dm_reg_name[reg_id];
u16 *prev = bq27xxx_dm_reg_ptr(buf, reg);
if (prev == NULL) {
dev_warn(di->dev, "buffer does not match %s dm spec\n", str);
return;
}
if (reg->bytes != 2) {
dev_warn(di->dev, "%s dm spec has unsupported byte size\n", str);
return;
}
if (!buf->has_data)
return;
if (be16_to_cpup(prev) == val) {
dev_info(di->dev, "%s has %u\n", str, val);
return;
}
#ifdef CONFIG_BATTERY_BQ27XXX_DT_UPDATES_NVM
if (!di->ram_chip && !bq27xxx_dt_to_nvm) {
#else
if (!di->ram_chip) {
#endif
/* devicetree and NVM differ; defer to NVM */
dev_warn(di->dev, "%s has %u; update to %u disallowed "
#ifdef CONFIG_BATTERY_BQ27XXX_DT_UPDATES_NVM
"by dt_monitored_battery_updates_nvm=0"
#else
"for flash/NVM data memory"
#endif
"\n", str, be16_to_cpup(prev), val);
return;
}
dev_info(di->dev, "update %s to %u\n", str, val);
*prev = cpu_to_be16(val);
buf->dirty = true;
}
static int bq27xxx_battery_cfgupdate_priv(struct bq27xxx_device_info *di, bool active)
{
const int limit = 100;
u16 cmd = active ? BQ27XXX_SET_CFGUPDATE : BQ27XXX_SOFT_RESET;
int ret, try = limit;
ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, cmd, false);
if (ret < 0)
return ret;
do {
BQ27XXX_MSLEEP(25);
ret = bq27xxx_read(di, BQ27XXX_REG_FLAGS, false);
if (ret < 0)
return ret;
} while (!!(ret & BQ27XXX_FLAG_CFGUP) != active && --try);
if (!try) {
dev_err(di->dev, "timed out waiting for cfgupdate flag %d\n", active);
return -EINVAL;
}
if (limit - try > 3)
dev_warn(di->dev, "cfgupdate %d, retries %d\n", active, limit - try);
return 0;
}
static inline int bq27xxx_battery_set_cfgupdate(struct bq27xxx_device_info *di)
{
int ret = bq27xxx_battery_cfgupdate_priv(di, true);
if (ret < 0 && ret != -EINVAL)
dev_err(di->dev, "bus error on set_cfgupdate: %d\n", ret);
return ret;
}
static inline int bq27xxx_battery_soft_reset(struct bq27xxx_device_info *di)
{
int ret = bq27xxx_battery_cfgupdate_priv(di, false);
if (ret < 0 && ret != -EINVAL)
dev_err(di->dev, "bus error on soft_reset: %d\n", ret);
return ret;
}
static int bq27xxx_battery_write_dm_block(struct bq27xxx_device_info *di,
struct bq27xxx_dm_buf *buf)
{
bool cfgup = di->chip == BQ27421; /* assume related chips need cfgupdate */
int ret;
if (!buf->dirty)
return 0;
if (cfgup) {
ret = bq27xxx_battery_set_cfgupdate(di);
if (ret < 0)
return ret;
}
ret = bq27xxx_write(di, BQ27XXX_DM_CTRL, 0, true);
if (ret < 0)
goto out;
ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true);
if (ret < 0)
goto out;
ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true);
if (ret < 0)
goto out;
BQ27XXX_MSLEEP(1);
ret = bq27xxx_write_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ);
if (ret < 0)
goto out;
ret = bq27xxx_write(di, BQ27XXX_DM_CKSUM,
bq27xxx_battery_checksum_dm_block(buf), true);
if (ret < 0)
goto out;
/* DO NOT read BQ27XXX_DM_CKSUM here to verify it! That may cause NVM
* corruption on the '425 chip (and perhaps others), which can damage
* the chip.
*/
if (cfgup) {
BQ27XXX_MSLEEP(1);
ret = bq27xxx_battery_soft_reset(di);
if (ret < 0)
return ret;
} else {
BQ27XXX_MSLEEP(100); /* flash DM updates in <100ms */
}
buf->dirty = false;
return 0;
out:
if (cfgup)
bq27xxx_battery_soft_reset(di);
dev_err(di->dev, "bus error writing chip memory: %d\n", ret);
return ret;
}
static void bq27xxx_battery_set_config(struct bq27xxx_device_info *di,
struct power_supply_battery_info *info)
{
struct bq27xxx_dm_buf bd = BQ27XXX_DM_BUF(di, BQ27XXX_DM_DESIGN_CAPACITY);
struct bq27xxx_dm_buf bt = BQ27XXX_DM_BUF(di, BQ27XXX_DM_TERMINATE_VOLTAGE);
bool updated;
if (bq27xxx_battery_unseal(di) < 0)
return;
if (info->charge_full_design_uah != -EINVAL &&
info->energy_full_design_uwh != -EINVAL) {
bq27xxx_battery_read_dm_block(di, &bd);
/* assume design energy & capacity are in same block */
bq27xxx_battery_update_dm_block(di, &bd,
BQ27XXX_DM_DESIGN_CAPACITY,
info->charge_full_design_uah / 1000);
bq27xxx_battery_update_dm_block(di, &bd,
BQ27XXX_DM_DESIGN_ENERGY,
info->energy_full_design_uwh / 1000);
}
if (info->voltage_min_design_uv != -EINVAL) {
bool same = bd.class == bt.class && bd.block == bt.block;
if (!same)
bq27xxx_battery_read_dm_block(di, &bt);
bq27xxx_battery_update_dm_block(di, same ? &bd : &bt,
BQ27XXX_DM_TERMINATE_VOLTAGE,
info->voltage_min_design_uv / 1000);
}
updated = bd.dirty || bt.dirty;
bq27xxx_battery_write_dm_block(di, &bd);
bq27xxx_battery_write_dm_block(di, &bt);
bq27xxx_battery_seal(di);
if (updated && di->chip != BQ27421) { /* not a cfgupdate chip, so reset */
bq27xxx_write(di, BQ27XXX_REG_CTRL, BQ27XXX_RESET, false);
BQ27XXX_MSLEEP(300); /* reset time is not documented */
}
/* assume bq27xxx_battery_update() is called hereafter */
}
static void bq27xxx_battery_settings(struct bq27xxx_device_info *di)
{
struct power_supply_battery_info info = {};
unsigned int min, max;
if (power_supply_get_battery_info(di->bat, &info) < 0)
return;
if (!di->dm_regs) {
dev_warn(di->dev, "data memory update not supported for chip\n");
return;
}
if (info.energy_full_design_uwh != info.charge_full_design_uah) {
if (info.energy_full_design_uwh == -EINVAL)
dev_warn(di->dev, "missing battery:energy-full-design-microwatt-hours\n");
else if (info.charge_full_design_uah == -EINVAL)
dev_warn(di->dev, "missing battery:charge-full-design-microamp-hours\n");
}
/* assume min == 0 */
max = di->dm_regs[BQ27XXX_DM_DESIGN_ENERGY].max;
if (info.energy_full_design_uwh > max * 1000) {
dev_err(di->dev, "invalid battery:energy-full-design-microwatt-hours %d\n",
info.energy_full_design_uwh);
info.energy_full_design_uwh = -EINVAL;
}
/* assume min == 0 */
max = di->dm_regs[BQ27XXX_DM_DESIGN_CAPACITY].max;
if (info.charge_full_design_uah > max * 1000) {
dev_err(di->dev, "invalid battery:charge-full-design-microamp-hours %d\n",
info.charge_full_design_uah);
info.charge_full_design_uah = -EINVAL;
}
min = di->dm_regs[BQ27XXX_DM_TERMINATE_VOLTAGE].min;
max = di->dm_regs[BQ27XXX_DM_TERMINATE_VOLTAGE].max;
if ((info.voltage_min_design_uv < min * 1000 ||
info.voltage_min_design_uv > max * 1000) &&
info.voltage_min_design_uv != -EINVAL) {
dev_err(di->dev, "invalid battery:voltage-min-design-microvolt %d\n",
info.voltage_min_design_uv);
info.voltage_min_design_uv = -EINVAL;
}
if ((info.energy_full_design_uwh != -EINVAL &&
info.charge_full_design_uah != -EINVAL) ||
info.voltage_min_design_uv != -EINVAL)
bq27xxx_battery_set_config(di, &info);
}
/*
@@ -1318,6 +1837,13 @@ static int bq27xxx_battery_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
ret = bq27xxx_simple_value(di->charge_design_full, val);
break;
/*
* TODO: Implement these to make registers set from
* power_supply_battery_info visible in sysfs.
*/
case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
return -EINVAL;
case POWER_SUPPLY_PROP_CYCLE_COUNT:
ret = bq27xxx_simple_value(di->cache.cycle_count, val);
break;
@@ -1351,7 +1877,10 @@ static void bq27xxx_external_power_changed(struct power_supply *psy)
int bq27xxx_battery_setup(struct bq27xxx_device_info *di)
{
struct power_supply_desc *psy_desc;
struct power_supply_config psy_cfg = { .drv_data = di, };
struct power_supply_config psy_cfg = {
.of_node = di->dev->of_node,
.drv_data = di,
};
INIT_DELAYED_WORK(&di->work, bq27xxx_battery_poll);
mutex_init(&di->lock);
@@ -1376,6 +1905,7 @@ int bq27xxx_battery_setup(struct bq27xxx_device_info *di)
dev_info(di->dev, "support ver. %s enabled\n", DRIVER_VERSION);
bq27xxx_battery_settings(di);
bq27xxx_battery_update(di);
mutex_lock(&bq27xxx_list_lock);

View File

@@ -38,7 +38,7 @@ static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg,
{
struct i2c_client *client = to_i2c_client(di->dev);
struct i2c_msg msg[2];
unsigned char data[2];
u8 data[2];
int ret;
if (!client->adapter)
@@ -68,6 +68,82 @@ static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg,
return ret;
}
static int bq27xxx_battery_i2c_write(struct bq27xxx_device_info *di, u8 reg,
int value, bool single)
{
struct i2c_client *client = to_i2c_client(di->dev);
struct i2c_msg msg;
u8 data[4];
int ret;
if (!client->adapter)
return -ENODEV;
data[0] = reg;
if (single) {
data[1] = (u8) value;
msg.len = 2;
} else {
put_unaligned_le16(value, &data[1]);
msg.len = 3;
}
msg.buf = data;
msg.addr = client->addr;
msg.flags = 0;
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0)
return ret;
if (ret != 1)
return -EINVAL;
return 0;
}
static int bq27xxx_battery_i2c_bulk_read(struct bq27xxx_device_info *di, u8 reg,
u8 *data, int len)
{
struct i2c_client *client = to_i2c_client(di->dev);
int ret;
if (!client->adapter)
return -ENODEV;
ret = i2c_smbus_read_i2c_block_data(client, reg, len, data);
if (ret < 0)
return ret;
if (ret != len)
return -EINVAL;
return 0;
}
static int bq27xxx_battery_i2c_bulk_write(struct bq27xxx_device_info *di,
u8 reg, u8 *data, int len)
{
struct i2c_client *client = to_i2c_client(di->dev);
struct i2c_msg msg;
u8 buf[33];
int ret;
if (!client->adapter)
return -ENODEV;
buf[0] = reg;
memcpy(&buf[1], data, len);
msg.buf = buf;
msg.addr = client->addr;
msg.flags = 0;
msg.len = len + 1;
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0)
return ret;
if (ret != 1)
return -EINVAL;
return 0;
}
static int bq27xxx_battery_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
@@ -95,7 +171,11 @@ static int bq27xxx_battery_i2c_probe(struct i2c_client *client,
di->dev = &client->dev;
di->chip = id->driver_data;
di->name = name;
di->bus.read = bq27xxx_battery_i2c_read;
di->bus.write = bq27xxx_battery_i2c_write;
di->bus.read_bulk = bq27xxx_battery_i2c_bulk_read;
di->bus.write_bulk = bq27xxx_battery_i2c_bulk_write;
ret = bq27xxx_battery_setup(di);
if (ret)

View File

@@ -0,0 +1,808 @@
/*
* Battery driver for CPCAP PMIC
*
* Copyright (C) 2017 Tony Lindgren <tony@atomide.com>
*
* Some parts of the code based on earlie Motorola mapphone Linux kernel
* drivers:
*
* Copyright (C) 2009-2010 Motorola, Inc.
*
* 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 "as is" WITHOUT ANY WARRANTY of any
* kind, whether express or implied; 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/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/reboot.h>
#include <linux/regmap.h>
#include <linux/iio/consumer.h>
#include <linux/iio/types.h>
#include <linux/mfd/motorola-cpcap.h>
#include <asm/div64.h>
/*
* Register bit defines for CPCAP_REG_BPEOL. Some of these seem to
* map to MC13783UG.pdf "Table 5-19. Register 13, Power Control 0"
* to enable BATTDETEN, LOBAT and EOL features. We currently use
* LOBAT interrupts instead of EOL.
*/
#define CPCAP_REG_BPEOL_BIT_EOL9 BIT(9) /* Set for EOL irq */
#define CPCAP_REG_BPEOL_BIT_EOL8 BIT(8) /* Set for EOL irq */
#define CPCAP_REG_BPEOL_BIT_UNKNOWN7 BIT(7)
#define CPCAP_REG_BPEOL_BIT_UNKNOWN6 BIT(6)
#define CPCAP_REG_BPEOL_BIT_UNKNOWN5 BIT(5)
#define CPCAP_REG_BPEOL_BIT_EOL_MULTI BIT(4) /* Set for multiple EOL irqs */
#define CPCAP_REG_BPEOL_BIT_UNKNOWN3 BIT(3)
#define CPCAP_REG_BPEOL_BIT_UNKNOWN2 BIT(2)
#define CPCAP_REG_BPEOL_BIT_BATTDETEN BIT(1) /* Enable battery detect */
#define CPCAP_REG_BPEOL_BIT_EOLSEL BIT(0) /* BPDET = 0, EOL = 1 */
#define CPCAP_BATTERY_CC_SAMPLE_PERIOD_MS 250
enum {
CPCAP_BATTERY_IIO_BATTDET,
CPCAP_BATTERY_IIO_VOLTAGE,
CPCAP_BATTERY_IIO_CHRG_CURRENT,
CPCAP_BATTERY_IIO_BATT_CURRENT,
CPCAP_BATTERY_IIO_NR,
};
enum cpcap_battery_irq_action {
CPCAP_BATTERY_IRQ_ACTION_NONE,
CPCAP_BATTERY_IRQ_ACTION_BATTERY_LOW,
CPCAP_BATTERY_IRQ_ACTION_POWEROFF,
};
struct cpcap_interrupt_desc {
const char *name;
struct list_head node;
int irq;
enum cpcap_battery_irq_action action;
};
struct cpcap_battery_config {
int ccm;
int cd_factor;
struct power_supply_info info;
};
struct cpcap_coulomb_counter_data {
s32 sample; /* 24-bits */
s32 accumulator;
s16 offset; /* 10-bits */
};
enum cpcap_battery_state {
CPCAP_BATTERY_STATE_PREVIOUS,
CPCAP_BATTERY_STATE_LATEST,
CPCAP_BATTERY_STATE_NR,
};
struct cpcap_battery_state_data {
int voltage;
int current_ua;
int counter_uah;
int temperature;
ktime_t time;
struct cpcap_coulomb_counter_data cc;
};
struct cpcap_battery_ddata {
struct device *dev;
struct regmap *reg;
struct list_head irq_list;
struct iio_channel *channels[CPCAP_BATTERY_IIO_NR];
struct power_supply *psy;
struct cpcap_battery_config config;
struct cpcap_battery_state_data state[CPCAP_BATTERY_STATE_NR];
atomic_t active;
int status;
u16 vendor;
};
#define CPCAP_NO_BATTERY -400
static struct cpcap_battery_state_data *
cpcap_battery_get_state(struct cpcap_battery_ddata *ddata,
enum cpcap_battery_state state)
{
if (state >= CPCAP_BATTERY_STATE_NR)
return NULL;
return &ddata->state[state];
}
static struct cpcap_battery_state_data *
cpcap_battery_latest(struct cpcap_battery_ddata *ddata)
{
return cpcap_battery_get_state(ddata, CPCAP_BATTERY_STATE_LATEST);
}
static struct cpcap_battery_state_data *
cpcap_battery_previous(struct cpcap_battery_ddata *ddata)
{
return cpcap_battery_get_state(ddata, CPCAP_BATTERY_STATE_PREVIOUS);
}
static int cpcap_charger_battery_temperature(struct cpcap_battery_ddata *ddata,
int *value)
{
struct iio_channel *channel;
int error;
channel = ddata->channels[CPCAP_BATTERY_IIO_BATTDET];
error = iio_read_channel_processed(channel, value);
if (error < 0) {
dev_warn(ddata->dev, "%s failed: %i\n", __func__, error);
*value = CPCAP_NO_BATTERY;
return error;
}
*value /= 100;
return 0;
}
static int cpcap_battery_get_voltage(struct cpcap_battery_ddata *ddata)
{
struct iio_channel *channel;
int error, value = 0;
channel = ddata->channels[CPCAP_BATTERY_IIO_VOLTAGE];
error = iio_read_channel_processed(channel, &value);
if (error < 0) {
dev_warn(ddata->dev, "%s failed: %i\n", __func__, error);
return 0;
}
return value * 1000;
}
static int cpcap_battery_get_current(struct cpcap_battery_ddata *ddata)
{
struct iio_channel *channel;
int error, value = 0;
channel = ddata->channels[CPCAP_BATTERY_IIO_BATT_CURRENT];
error = iio_read_channel_processed(channel, &value);
if (error < 0) {
dev_warn(ddata->dev, "%s failed: %i\n", __func__, error);
return 0;
}
return value * 1000;
}
/**
* cpcap_battery_cc_raw_div - calculate and divide coulomb counter μAms values
* @ddata: device driver data
* @sample: coulomb counter sample value
* @accumulator: coulomb counter integrator value
* @offset: coulomb counter offset value
* @divider: conversion divider
*
* Note that cc_lsb and cc_dur values are from Motorola Linux kernel
* function data_get_avg_curr_ua() and seem to be based on measured test
* results. It also has the following comment:
*
* Adjustment factors are applied here as a temp solution per the test
* results. Need to work out a formal solution for this adjustment.
*
* A coulomb counter for similar hardware seems to be documented in
* "TWL6030 Gas Gauging Basics (Rev. A)" swca095a.pdf in chapter
* "10 Calculating Accumulated Current". We however follow what the
* Motorola mapphone Linux kernel is doing as there may be either a
* TI or ST coulomb counter in the PMIC.
*/
static int cpcap_battery_cc_raw_div(struct cpcap_battery_ddata *ddata,
u32 sample, s32 accumulator,
s16 offset, u32 divider)
{
s64 acc;
u64 tmp;
int avg_current;
u32 cc_lsb;
sample &= 0xffffff; /* 24-bits, unsigned */
offset &= 0x7ff; /* 10-bits, signed */
switch (ddata->vendor) {
case CPCAP_VENDOR_ST:
cc_lsb = 95374; /* μAms per LSB */
break;
case CPCAP_VENDOR_TI:
cc_lsb = 91501; /* μAms per LSB */
break;
default:
return -EINVAL;
}
acc = accumulator;
acc = acc - ((s64)sample * offset);
cc_lsb = (cc_lsb * ddata->config.cd_factor) / 1000;
if (acc >= 0)
tmp = acc;
else
tmp = acc * -1;
tmp = tmp * cc_lsb;
do_div(tmp, divider);
avg_current = tmp;
if (acc >= 0)
return -avg_current;
else
return avg_current;
}
/* 3600000μAms = 1μAh */
static int cpcap_battery_cc_to_uah(struct cpcap_battery_ddata *ddata,
u32 sample, s32 accumulator,
s16 offset)
{
return cpcap_battery_cc_raw_div(ddata, sample,
accumulator, offset,
3600000);
}
static int cpcap_battery_cc_to_ua(struct cpcap_battery_ddata *ddata,
u32 sample, s32 accumulator,
s16 offset)
{
return cpcap_battery_cc_raw_div(ddata, sample,
accumulator, offset,
sample *
CPCAP_BATTERY_CC_SAMPLE_PERIOD_MS);
}
/**
* cpcap_battery_read_accumulated - reads cpcap coulomb counter
* @ddata: device driver data
* @regs: coulomb counter values
*
* Based on Motorola mapphone kernel function data_read_regs().
* Looking at the registers, the coulomb counter seems similar to
* the coulomb counter in TWL6030. See "TWL6030 Gas Gauging Basics
* (Rev. A) swca095a.pdf for "10 Calculating Accumulated Current".
*
* Note that swca095a.pdf instructs to stop the coulomb counter
* before reading to avoid values changing. Motorola mapphone
* Linux kernel does not do it, so let's assume they've verified
* the data produced is correct.
*/
static int
cpcap_battery_read_accumulated(struct cpcap_battery_ddata *ddata,
struct cpcap_coulomb_counter_data *ccd)
{
u16 buf[7]; /* CPCAP_REG_CC1 to CCI */
int error;
ccd->sample = 0;
ccd->accumulator = 0;
ccd->offset = 0;
/* Read coulomb counter register range */
error = regmap_bulk_read(ddata->reg, CPCAP_REG_CCS1,
buf, ARRAY_SIZE(buf));
if (error)
return 0;
/* Sample value CPCAP_REG_CCS1 & 2 */
ccd->sample = (buf[1] & 0x0fff) << 16;
ccd->sample |= buf[0];
/* Accumulator value CPCAP_REG_CCA1 & 2 */
ccd->accumulator = ((s16)buf[3]) << 16;
ccd->accumulator |= buf[2];
/* Offset value CPCAP_REG_CCO */
ccd->offset = buf[5];
/* Adjust offset based on mode value CPCAP_REG_CCM? */
if (buf[4] >= 0x200)
ccd->offset |= 0xfc00;
return cpcap_battery_cc_to_uah(ddata,
ccd->sample,
ccd->accumulator,
ccd->offset);
}
/**
* cpcap_battery_cc_get_avg_current - read cpcap coulumb counter
* @ddata: cpcap battery driver device data
*/
static int cpcap_battery_cc_get_avg_current(struct cpcap_battery_ddata *ddata)
{
int value, acc, error;
s32 sample = 1;
s16 offset;
if (ddata->vendor == CPCAP_VENDOR_ST)
sample = 4;
/* Coulomb counter integrator */
error = regmap_read(ddata->reg, CPCAP_REG_CCI, &value);
if (error)
return error;
if ((ddata->vendor == CPCAP_VENDOR_TI) && (value > 0x2000))
value = value | 0xc000;
acc = (s16)value;
/* Coulomb counter sample time */
error = regmap_read(ddata->reg, CPCAP_REG_CCM, &value);
if (error)
return error;
if (value < 0x200)
offset = value;
else
offset = value | 0xfc00;
return cpcap_battery_cc_to_ua(ddata, sample, acc, offset);
}
static bool cpcap_battery_full(struct cpcap_battery_ddata *ddata)
{
struct cpcap_battery_state_data *state = cpcap_battery_latest(ddata);
/* Basically anything that measures above 4347000 is full */
if (state->voltage >= (ddata->config.info.voltage_max_design - 4000))
return true;
return false;
}
static int cpcap_battery_update_status(struct cpcap_battery_ddata *ddata)
{
struct cpcap_battery_state_data state, *latest, *previous;
ktime_t now;
int error;
memset(&state, 0, sizeof(state));
now = ktime_get();
latest = cpcap_battery_latest(ddata);
if (latest) {
s64 delta_ms = ktime_to_ms(ktime_sub(now, latest->time));
if (delta_ms < CPCAP_BATTERY_CC_SAMPLE_PERIOD_MS)
return delta_ms;
}
state.time = now;
state.voltage = cpcap_battery_get_voltage(ddata);
state.current_ua = cpcap_battery_get_current(ddata);
state.counter_uah = cpcap_battery_read_accumulated(ddata, &state.cc);
error = cpcap_charger_battery_temperature(ddata,
&state.temperature);
if (error)
return error;
previous = cpcap_battery_previous(ddata);
memcpy(previous, latest, sizeof(*previous));
memcpy(latest, &state, sizeof(*latest));
return 0;
}
static enum power_supply_property cpcap_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_CURRENT_AVG,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CHARGE_COUNTER,
POWER_SUPPLY_PROP_POWER_NOW,
POWER_SUPPLY_PROP_POWER_AVG,
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_SCOPE,
POWER_SUPPLY_PROP_TEMP,
};
static int cpcap_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct cpcap_battery_ddata *ddata = power_supply_get_drvdata(psy);
struct cpcap_battery_state_data *latest, *previous;
u32 sample;
s32 accumulator;
int cached;
s64 tmp;
cached = cpcap_battery_update_status(ddata);
if (cached < 0)
return cached;
latest = cpcap_battery_latest(ddata);
previous = cpcap_battery_previous(ddata);
switch (psp) {
case POWER_SUPPLY_PROP_PRESENT:
if (latest->temperature > CPCAP_NO_BATTERY)
val->intval = 1;
else
val->intval = 0;
break;
case POWER_SUPPLY_PROP_STATUS:
if (cpcap_battery_full(ddata)) {
val->intval = POWER_SUPPLY_STATUS_FULL;
break;
}
if (cpcap_battery_cc_get_avg_current(ddata) < 0)
val->intval = POWER_SUPPLY_STATUS_CHARGING;
else
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = ddata->config.info.technology;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = cpcap_battery_get_voltage(ddata);
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
val->intval = ddata->config.info.voltage_max_design;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
val->intval = ddata->config.info.voltage_min_design;
break;
case POWER_SUPPLY_PROP_CURRENT_AVG:
if (cached) {
val->intval = cpcap_battery_cc_get_avg_current(ddata);
break;
}
sample = latest->cc.sample - previous->cc.sample;
accumulator = latest->cc.accumulator - previous->cc.accumulator;
val->intval = cpcap_battery_cc_to_ua(ddata, sample,
accumulator,
latest->cc.offset);
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
val->intval = latest->current_ua;
break;
case POWER_SUPPLY_PROP_CHARGE_COUNTER:
val->intval = latest->counter_uah;
break;
case POWER_SUPPLY_PROP_POWER_NOW:
tmp = (latest->voltage / 10000) * latest->current_ua;
val->intval = div64_s64(tmp, 100);
break;
case POWER_SUPPLY_PROP_POWER_AVG:
if (cached) {
tmp = cpcap_battery_cc_get_avg_current(ddata);
tmp *= (latest->voltage / 10000);
val->intval = div64_s64(tmp, 100);
break;
}
sample = latest->cc.sample - previous->cc.sample;
accumulator = latest->cc.accumulator - previous->cc.accumulator;
tmp = cpcap_battery_cc_to_ua(ddata, sample, accumulator,
latest->cc.offset);
tmp *= ((latest->voltage + previous->voltage) / 20000);
val->intval = div64_s64(tmp, 100);
break;
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
if (cpcap_battery_full(ddata))
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
else if (latest->voltage >= 3750000)
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_HIGH;
else if (latest->voltage >= 3300000)
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
else if (latest->voltage > 3100000)
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
else if (latest->voltage <= 3100000)
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
else
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
val->intval = ddata->config.info.charge_full_design;
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
break;
case POWER_SUPPLY_PROP_TEMP:
val->intval = latest->temperature;
break;
default:
return -EINVAL;
}
return 0;
}
static irqreturn_t cpcap_battery_irq_thread(int irq, void *data)
{
struct cpcap_battery_ddata *ddata = data;
struct cpcap_battery_state_data *latest;
struct cpcap_interrupt_desc *d;
if (!atomic_read(&ddata->active))
return IRQ_NONE;
list_for_each_entry(d, &ddata->irq_list, node) {
if (irq == d->irq)
break;
}
if (!d)
return IRQ_NONE;
latest = cpcap_battery_latest(ddata);
switch (d->action) {
case CPCAP_BATTERY_IRQ_ACTION_BATTERY_LOW:
if (latest->counter_uah >= 0)
dev_warn(ddata->dev, "Battery low at 3.3V!\n");
break;
case CPCAP_BATTERY_IRQ_ACTION_POWEROFF:
if (latest->counter_uah >= 0) {
dev_emerg(ddata->dev,
"Battery empty at 3.1V, powering off\n");
orderly_poweroff(true);
}
break;
default:
break;
}
power_supply_changed(ddata->psy);
return IRQ_HANDLED;
}
static int cpcap_battery_init_irq(struct platform_device *pdev,
struct cpcap_battery_ddata *ddata,
const char *name)
{
struct cpcap_interrupt_desc *d;
int irq, error;
irq = platform_get_irq_byname(pdev, name);
if (!irq)
return -ENODEV;
error = devm_request_threaded_irq(ddata->dev, irq, NULL,
cpcap_battery_irq_thread,
IRQF_SHARED,
name, ddata);
if (error) {
dev_err(ddata->dev, "could not get irq %s: %i\n",
name, error);
return error;
}
d = devm_kzalloc(ddata->dev, sizeof(*d), GFP_KERNEL);
if (!d)
return -ENOMEM;
d->name = name;
d->irq = irq;
if (!strncmp(name, "lowbph", 6))
d->action = CPCAP_BATTERY_IRQ_ACTION_BATTERY_LOW;
else if (!strncmp(name, "lowbpl", 6))
d->action = CPCAP_BATTERY_IRQ_ACTION_POWEROFF;
list_add(&d->node, &ddata->irq_list);
return 0;
}
static int cpcap_battery_init_interrupts(struct platform_device *pdev,
struct cpcap_battery_ddata *ddata)
{
const char * const cpcap_battery_irqs[] = {
"eol", "lowbph", "lowbpl",
"chrgcurr1", "battdetb"
};
int i, error;
for (i = 0; i < ARRAY_SIZE(cpcap_battery_irqs); i++) {
error = cpcap_battery_init_irq(pdev, ddata,
cpcap_battery_irqs[i]);
if (error)
return error;
}
/* Enable low battery interrupts for 3.3V high and 3.1V low */
error = regmap_update_bits(ddata->reg, CPCAP_REG_BPEOL,
0xffff,
CPCAP_REG_BPEOL_BIT_BATTDETEN);
if (error)
return error;
return 0;
}
static int cpcap_battery_init_iio(struct cpcap_battery_ddata *ddata)
{
const char * const names[CPCAP_BATTERY_IIO_NR] = {
"battdetb", "battp", "chg_isense", "batti",
};
int error, i;
for (i = 0; i < CPCAP_BATTERY_IIO_NR; i++) {
ddata->channels[i] = devm_iio_channel_get(ddata->dev,
names[i]);
if (IS_ERR(ddata->channels[i])) {
error = PTR_ERR(ddata->channels[i]);
goto out_err;
}
if (!ddata->channels[i]->indio_dev) {
error = -ENXIO;
goto out_err;
}
}
return 0;
out_err:
dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n",
error);
return error;
}
/*
* Based on the values from Motorola mapphone Linux kernel. In the
* the Motorola mapphone Linux kernel tree the value for pm_cd_factor
* is passed to the kernel via device tree. If it turns out to be
* something device specific we can consider that too later.
*
* And looking at the battery full and shutdown values for the stock
* kernel on droid 4, full is 4351000 and software initiates shutdown
* at 3078000. The device will die around 2743000.
*/
static const struct cpcap_battery_config cpcap_battery_default_data = {
.ccm = 0x3ff,
.cd_factor = 0x3cc,
.info.technology = POWER_SUPPLY_TECHNOLOGY_LION,
.info.voltage_max_design = 4351000,
.info.voltage_min_design = 3100000,
.info.charge_full_design = 1740000,
};
#ifdef CONFIG_OF
static const struct of_device_id cpcap_battery_id_table[] = {
{
.compatible = "motorola,cpcap-battery",
.data = &cpcap_battery_default_data,
},
{},
};
MODULE_DEVICE_TABLE(of, cpcap_battery_id_table);
#endif
static int cpcap_battery_probe(struct platform_device *pdev)
{
struct power_supply_desc *psy_desc;
struct cpcap_battery_ddata *ddata;
const struct of_device_id *match;
struct power_supply_config psy_cfg = {};
int error;
match = of_match_device(of_match_ptr(cpcap_battery_id_table),
&pdev->dev);
if (!match)
return -EINVAL;
if (!match->data) {
dev_err(&pdev->dev, "no configuration data found\n");
return -ENODEV;
}
ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata)
return -ENOMEM;
INIT_LIST_HEAD(&ddata->irq_list);
ddata->dev = &pdev->dev;
memcpy(&ddata->config, match->data, sizeof(ddata->config));
ddata->reg = dev_get_regmap(ddata->dev->parent, NULL);
if (!ddata->reg)
return -ENODEV;
error = cpcap_get_vendor(ddata->dev, ddata->reg, &ddata->vendor);
if (error)
return error;
platform_set_drvdata(pdev, ddata);
error = regmap_update_bits(ddata->reg, CPCAP_REG_CCM,
0xffff, ddata->config.ccm);
if (error)
return error;
error = cpcap_battery_init_interrupts(pdev, ddata);
if (error)
return error;
error = cpcap_battery_init_iio(ddata);
if (error)
return error;
psy_desc = devm_kzalloc(ddata->dev, sizeof(*psy_desc), GFP_KERNEL);
if (!psy_desc)
return -ENOMEM;
psy_desc->name = "battery",
psy_desc->type = POWER_SUPPLY_TYPE_BATTERY,
psy_desc->properties = cpcap_battery_props,
psy_desc->num_properties = ARRAY_SIZE(cpcap_battery_props),
psy_desc->get_property = cpcap_battery_get_property,
psy_cfg.of_node = pdev->dev.of_node;
psy_cfg.drv_data = ddata;
ddata->psy = devm_power_supply_register(ddata->dev, psy_desc,
&psy_cfg);
error = PTR_ERR_OR_ZERO(ddata->psy);
if (error) {
dev_err(ddata->dev, "failed to register power supply\n");
return error;
}
atomic_set(&ddata->active, 1);
return 0;
}
static int cpcap_battery_remove(struct platform_device *pdev)
{
struct cpcap_battery_ddata *ddata = platform_get_drvdata(pdev);
int error;
atomic_set(&ddata->active, 0);
error = regmap_update_bits(ddata->reg, CPCAP_REG_BPEOL,
0xffff, 0);
if (error)
dev_err(&pdev->dev, "could not disable: %i\n", error);
return 0;
}
static struct platform_driver cpcap_battery_driver = {
.driver = {
.name = "cpcap_battery",
.of_match_table = of_match_ptr(cpcap_battery_id_table),
},
.probe = cpcap_battery_probe,
.remove = cpcap_battery_remove,
};
module_platform_driver(cpcap_battery_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
MODULE_DESCRIPTION("CPCAP PMIC Battery Driver");

View File

@@ -38,20 +38,27 @@
#include <linux/iio/consumer.h>
#include <linux/mfd/motorola-cpcap.h>
/* CPCAP_REG_CRM register bits */
/*
* CPCAP_REG_CRM register bits. For documentation of somewhat similar hardware,
* see NXP "MC13783 Power Management and Audio Circuit Users's Guide"
* MC13783UG.pdf chapter "8.5 Battery Interface Register Summary". The registers
* and values for CPCAP are different, but some of the internal components seem
* similar. Also see the Motorola Linux kernel cpcap-regbits.h. CPCAP_REG_CHRGR_1
* bits that seem to describe the CRM register.
*/
#define CPCAP_REG_CRM_UNUSED_641_15 BIT(15) /* 641 = register number */
#define CPCAP_REG_CRM_UNUSED_641_14 BIT(14) /* 641 = register number */
#define CPCAP_REG_CRM_CHRG_LED_EN BIT(13)
#define CPCAP_REG_CRM_RVRSMODE BIT(12)
#define CPCAP_REG_CRM_ICHRG_TR1 BIT(11)
#define CPCAP_REG_CRM_CHRG_LED_EN BIT(13) /* Charger LED */
#define CPCAP_REG_CRM_RVRSMODE BIT(12) /* USB VBUS output enable */
#define CPCAP_REG_CRM_ICHRG_TR1 BIT(11) /* Trickle charge current */
#define CPCAP_REG_CRM_ICHRG_TR0 BIT(10)
#define CPCAP_REG_CRM_FET_OVRD BIT(9)
#define CPCAP_REG_CRM_FET_CTRL BIT(8)
#define CPCAP_REG_CRM_VCHRG3 BIT(7)
#define CPCAP_REG_CRM_FET_OVRD BIT(9) /* 0 = hardware, 1 = FET_CTRL */
#define CPCAP_REG_CRM_FET_CTRL BIT(8) /* BPFET 1 if FET_OVRD set */
#define CPCAP_REG_CRM_VCHRG3 BIT(7) /* Charge voltage bits */
#define CPCAP_REG_CRM_VCHRG2 BIT(6)
#define CPCAP_REG_CRM_VCHRG1 BIT(5)
#define CPCAP_REG_CRM_VCHRG0 BIT(4)
#define CPCAP_REG_CRM_ICHRG3 BIT(3)
#define CPCAP_REG_CRM_ICHRG3 BIT(3) /* Charge current bits */
#define CPCAP_REG_CRM_ICHRG2 BIT(2)
#define CPCAP_REG_CRM_ICHRG1 BIT(1)
#define CPCAP_REG_CRM_ICHRG0 BIT(0)
@@ -63,42 +70,50 @@
#define CPCAP_REG_CRM_TR_0A48 CPCAP_REG_CRM_TR(0x2)
#define CPCAP_REG_CRM_TR_0A72 CPCAP_REG_CRM_TR(0x4)
/* CPCAP_REG_CRM charge voltages */
/*
* CPCAP_REG_CRM charge voltages based on the ADC channel 1 values.
* Note that these register bits don't match MC13783UG.pdf VCHRG
* register bits.
*/
#define CPCAP_REG_CRM_VCHRG(val) (((val) & 0xf) << 4)
#define CPCAP_REG_CRM_VCHRG_3V80 CPCAP_REG_CRM_VCHRG(0x0)
#define CPCAP_REG_CRM_VCHRG_4V10 CPCAP_REG_CRM_VCHRG(0x1)
#define CPCAP_REG_CRM_VCHRG_4V15 CPCAP_REG_CRM_VCHRG(0x2)
#define CPCAP_REG_CRM_VCHRG_4V20 CPCAP_REG_CRM_VCHRG(0x3)
#define CPCAP_REG_CRM_VCHRG_4V22 CPCAP_REG_CRM_VCHRG(0x4)
#define CPCAP_REG_CRM_VCHRG_4V24 CPCAP_REG_CRM_VCHRG(0x5)
#define CPCAP_REG_CRM_VCHRG_4V26 CPCAP_REG_CRM_VCHRG(0x6)
#define CPCAP_REG_CRM_VCHRG_4V28 CPCAP_REG_CRM_VCHRG(0x7)
#define CPCAP_REG_CRM_VCHRG_4V30 CPCAP_REG_CRM_VCHRG(0x8)
#define CPCAP_REG_CRM_VCHRG_4V32 CPCAP_REG_CRM_VCHRG(0x9)
#define CPCAP_REG_CRM_VCHRG_4V34 CPCAP_REG_CRM_VCHRG(0xa)
#define CPCAP_REG_CRM_VCHRG_4V12 CPCAP_REG_CRM_VCHRG(0x2)
#define CPCAP_REG_CRM_VCHRG_4V15 CPCAP_REG_CRM_VCHRG(0x3)
#define CPCAP_REG_CRM_VCHRG_4V17 CPCAP_REG_CRM_VCHRG(0x4)
#define CPCAP_REG_CRM_VCHRG_4V20 CPCAP_REG_CRM_VCHRG(0x5)
#define CPCAP_REG_CRM_VCHRG_4V23 CPCAP_REG_CRM_VCHRG(0x6)
#define CPCAP_REG_CRM_VCHRG_4V25 CPCAP_REG_CRM_VCHRG(0x7)
#define CPCAP_REG_CRM_VCHRG_4V27 CPCAP_REG_CRM_VCHRG(0x8)
#define CPCAP_REG_CRM_VCHRG_4V30 CPCAP_REG_CRM_VCHRG(0x9)
#define CPCAP_REG_CRM_VCHRG_4V33 CPCAP_REG_CRM_VCHRG(0xa)
#define CPCAP_REG_CRM_VCHRG_4V35 CPCAP_REG_CRM_VCHRG(0xb)
#define CPCAP_REG_CRM_VCHRG_4V38 CPCAP_REG_CRM_VCHRG(0xc)
#define CPCAP_REG_CRM_VCHRG_4V40 CPCAP_REG_CRM_VCHRG(0xd)
#define CPCAP_REG_CRM_VCHRG_4V42 CPCAP_REG_CRM_VCHRG(0xe)
#define CPCAP_REG_CRM_VCHRG_4V44 CPCAP_REG_CRM_VCHRG(0xf)
/* CPCAP_REG_CRM charge currents */
/*
* CPCAP_REG_CRM charge currents. These seem to match MC13783UG.pdf
* values in "Table 8-3. Charge Path Regulator Current Limit
* Characteristics" for the nominal values.
*/
#define CPCAP_REG_CRM_ICHRG(val) (((val) & 0xf) << 0)
#define CPCAP_REG_CRM_ICHRG_0A000 CPCAP_REG_CRM_ICHRG(0x0)
#define CPCAP_REG_CRM_ICHRG_0A070 CPCAP_REG_CRM_ICHRG(0x1)
#define CPCAP_REG_CRM_ICHRG_0A176 CPCAP_REG_CRM_ICHRG(0x2)
#define CPCAP_REG_CRM_ICHRG_0A264 CPCAP_REG_CRM_ICHRG(0x3)
#define CPCAP_REG_CRM_ICHRG_0A352 CPCAP_REG_CRM_ICHRG(0x4)
#define CPCAP_REG_CRM_ICHRG_0A440 CPCAP_REG_CRM_ICHRG(0x5)
#define CPCAP_REG_CRM_ICHRG_0A528 CPCAP_REG_CRM_ICHRG(0x6)
#define CPCAP_REG_CRM_ICHRG_0A616 CPCAP_REG_CRM_ICHRG(0x7)
#define CPCAP_REG_CRM_ICHRG_0A704 CPCAP_REG_CRM_ICHRG(0x8)
#define CPCAP_REG_CRM_ICHRG_0A792 CPCAP_REG_CRM_ICHRG(0x9)
#define CPCAP_REG_CRM_ICHRG_0A880 CPCAP_REG_CRM_ICHRG(0xa)
#define CPCAP_REG_CRM_ICHRG_0A968 CPCAP_REG_CRM_ICHRG(0xb)
#define CPCAP_REG_CRM_ICHRG_1A056 CPCAP_REG_CRM_ICHRG(0xc)
#define CPCAP_REG_CRM_ICHRG_1A144 CPCAP_REG_CRM_ICHRG(0xd)
#define CPCAP_REG_CRM_ICHRG_1A584 CPCAP_REG_CRM_ICHRG(0xe)
#define CPCAP_REG_CRM_ICHRG_0A177 CPCAP_REG_CRM_ICHRG(0x2)
#define CPCAP_REG_CRM_ICHRG_0A266 CPCAP_REG_CRM_ICHRG(0x3)
#define CPCAP_REG_CRM_ICHRG_0A355 CPCAP_REG_CRM_ICHRG(0x4)
#define CPCAP_REG_CRM_ICHRG_0A443 CPCAP_REG_CRM_ICHRG(0x5)
#define CPCAP_REG_CRM_ICHRG_0A532 CPCAP_REG_CRM_ICHRG(0x6)
#define CPCAP_REG_CRM_ICHRG_0A621 CPCAP_REG_CRM_ICHRG(0x7)
#define CPCAP_REG_CRM_ICHRG_0A709 CPCAP_REG_CRM_ICHRG(0x8)
#define CPCAP_REG_CRM_ICHRG_0A798 CPCAP_REG_CRM_ICHRG(0x9)
#define CPCAP_REG_CRM_ICHRG_0A886 CPCAP_REG_CRM_ICHRG(0xa)
#define CPCAP_REG_CRM_ICHRG_0A975 CPCAP_REG_CRM_ICHRG(0xb)
#define CPCAP_REG_CRM_ICHRG_1A064 CPCAP_REG_CRM_ICHRG(0xc)
#define CPCAP_REG_CRM_ICHRG_1A152 CPCAP_REG_CRM_ICHRG(0xd)
#define CPCAP_REG_CRM_ICHRG_1A596 CPCAP_REG_CRM_ICHRG(0xe)
#define CPCAP_REG_CRM_ICHRG_NO_LIMIT CPCAP_REG_CRM_ICHRG(0xf)
enum {
@@ -428,9 +443,9 @@ static void cpcap_usb_detect(struct work_struct *work)
int max_current;
if (cpcap_charger_battery_found(ddata))
max_current = CPCAP_REG_CRM_ICHRG_1A584;
max_current = CPCAP_REG_CRM_ICHRG_1A596;
else
max_current = CPCAP_REG_CRM_ICHRG_0A528;
max_current = CPCAP_REG_CRM_ICHRG_0A532;
error = cpcap_charger_set_state(ddata,
CPCAP_REG_CRM_VCHRG_4V35,
@@ -586,6 +601,7 @@ static int cpcap_charger_probe(struct platform_device *pdev)
{
struct cpcap_charger_ddata *ddata;
const struct of_device_id *of_id;
struct power_supply_config psy_cfg = {};
int error;
of_id = of_match_device(of_match_ptr(cpcap_charger_id_table),
@@ -614,9 +630,12 @@ static int cpcap_charger_probe(struct platform_device *pdev)
atomic_set(&ddata->active, 1);
psy_cfg.of_node = pdev->dev.of_node;
psy_cfg.drv_data = ddata;
ddata->usb = devm_power_supply_register(ddata->dev,
&cpcap_charger_usb_desc,
NULL);
&psy_cfg);
if (IS_ERR(ddata->usb)) {
error = PTR_ERR(ddata->usb);
dev_err(ddata->dev, "failed to register USB charger: %i\n",

View File

@@ -0,0 +1,210 @@
/*
* Copyright (C) 2017, Topic Embedded Products
* Driver for LTC3651 charger IC.
*
* 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.
*/
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/slab.h>
#include <linux/of.h>
struct ltc3651_charger {
struct power_supply *charger;
struct power_supply_desc charger_desc;
struct gpio_desc *acpr_gpio;
struct gpio_desc *fault_gpio;
struct gpio_desc *chrg_gpio;
};
static irqreturn_t ltc3651_charger_irq(int irq, void *devid)
{
struct power_supply *charger = devid;
power_supply_changed(charger);
return IRQ_HANDLED;
}
static inline struct ltc3651_charger *psy_to_ltc3651_charger(
struct power_supply *psy)
{
return power_supply_get_drvdata(psy);
}
static int ltc3651_charger_get_property(struct power_supply *psy,
enum power_supply_property psp, union power_supply_propval *val)
{
struct ltc3651_charger *ltc3651_charger = psy_to_ltc3651_charger(psy);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
if (!ltc3651_charger->chrg_gpio) {
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
break;
}
if (gpiod_get_value(ltc3651_charger->chrg_gpio))
val->intval = POWER_SUPPLY_STATUS_CHARGING;
else
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
case POWER_SUPPLY_PROP_ONLINE:
val->intval = gpiod_get_value(ltc3651_charger->acpr_gpio);
break;
case POWER_SUPPLY_PROP_HEALTH:
if (!ltc3651_charger->fault_gpio) {
val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
break;
}
if (!gpiod_get_value(ltc3651_charger->fault_gpio)) {
val->intval = POWER_SUPPLY_HEALTH_GOOD;
break;
}
/*
* If the fault pin is active, the chrg pin explains the type
* of failure.
*/
if (!ltc3651_charger->chrg_gpio) {
val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
break;
}
val->intval = gpiod_get_value(ltc3651_charger->chrg_gpio) ?
POWER_SUPPLY_HEALTH_OVERHEAT :
POWER_SUPPLY_HEALTH_DEAD;
break;
default:
return -EINVAL;
}
return 0;
}
static enum power_supply_property ltc3651_charger_properties[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_HEALTH,
};
static int ltc3651_charger_probe(struct platform_device *pdev)
{
struct power_supply_config psy_cfg = {};
struct ltc3651_charger *ltc3651_charger;
struct power_supply_desc *charger_desc;
int ret;
ltc3651_charger = devm_kzalloc(&pdev->dev, sizeof(*ltc3651_charger),
GFP_KERNEL);
if (!ltc3651_charger)
return -ENOMEM;
ltc3651_charger->acpr_gpio = devm_gpiod_get(&pdev->dev,
"lltc,acpr", GPIOD_IN);
if (IS_ERR(ltc3651_charger->acpr_gpio)) {
ret = PTR_ERR(ltc3651_charger->acpr_gpio);
dev_err(&pdev->dev, "Failed to acquire acpr GPIO: %d\n", ret);
return ret;
}
ltc3651_charger->fault_gpio = devm_gpiod_get_optional(&pdev->dev,
"lltc,fault", GPIOD_IN);
if (IS_ERR(ltc3651_charger->fault_gpio)) {
ret = PTR_ERR(ltc3651_charger->fault_gpio);
dev_err(&pdev->dev, "Failed to acquire fault GPIO: %d\n", ret);
return ret;
}
ltc3651_charger->chrg_gpio = devm_gpiod_get_optional(&pdev->dev,
"lltc,chrg", GPIOD_IN);
if (IS_ERR(ltc3651_charger->chrg_gpio)) {
ret = PTR_ERR(ltc3651_charger->chrg_gpio);
dev_err(&pdev->dev, "Failed to acquire chrg GPIO: %d\n", ret);
return ret;
}
charger_desc = &ltc3651_charger->charger_desc;
charger_desc->name = pdev->dev.of_node->name;
charger_desc->type = POWER_SUPPLY_TYPE_MAINS;
charger_desc->properties = ltc3651_charger_properties;
charger_desc->num_properties = ARRAY_SIZE(ltc3651_charger_properties);
charger_desc->get_property = ltc3651_charger_get_property;
psy_cfg.of_node = pdev->dev.of_node;
psy_cfg.drv_data = ltc3651_charger;
ltc3651_charger->charger = devm_power_supply_register(&pdev->dev,
charger_desc, &psy_cfg);
if (IS_ERR(ltc3651_charger->charger)) {
ret = PTR_ERR(ltc3651_charger->charger);
dev_err(&pdev->dev, "Failed to register power supply: %d\n",
ret);
return ret;
}
/*
* Acquire IRQs for the GPIO pins if possible. If the system does not
* support IRQs on these pins, userspace will have to poll the sysfs
* files manually.
*/
if (ltc3651_charger->acpr_gpio) {
ret = gpiod_to_irq(ltc3651_charger->acpr_gpio);
if (ret >= 0)
ret = devm_request_any_context_irq(&pdev->dev, ret,
ltc3651_charger_irq,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
dev_name(&pdev->dev), ltc3651_charger->charger);
if (ret < 0)
dev_warn(&pdev->dev, "Failed to request acpr irq\n");
}
if (ltc3651_charger->fault_gpio) {
ret = gpiod_to_irq(ltc3651_charger->fault_gpio);
if (ret >= 0)
ret = devm_request_any_context_irq(&pdev->dev, ret,
ltc3651_charger_irq,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
dev_name(&pdev->dev), ltc3651_charger->charger);
if (ret < 0)
dev_warn(&pdev->dev, "Failed to request fault irq\n");
}
if (ltc3651_charger->chrg_gpio) {
ret = gpiod_to_irq(ltc3651_charger->chrg_gpio);
if (ret >= 0)
ret = devm_request_any_context_irq(&pdev->dev, ret,
ltc3651_charger_irq,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
dev_name(&pdev->dev), ltc3651_charger->charger);
if (ret < 0)
dev_warn(&pdev->dev, "Failed to request chrg irq\n");
}
platform_set_drvdata(pdev, ltc3651_charger);
return 0;
}
static const struct of_device_id ltc3651_charger_match[] = {
{ .compatible = "lltc,ltc3651-charger" },
{ }
};
MODULE_DEVICE_TABLE(of, ltc3651_charger_match);
static struct platform_driver ltc3651_charger_driver = {
.probe = ltc3651_charger_probe,
.driver = {
.name = "ltc3651-charger",
.of_match_table = ltc3651_charger_match,
},
};
module_platform_driver(ltc3651_charger_driver);
MODULE_AUTHOR("Mike Looijmans <mike.looijmans@topic.nl>");
MODULE_DESCRIPTION("Driver for LTC3651 charger");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:ltc3651-charger");

View File

@@ -17,6 +17,7 @@
#include <linux/device.h>
#include <linux/notifier.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/power_supply.h>
#include <linux/thermal.h>
#include "power_supply.h"
@@ -274,8 +275,30 @@ static int power_supply_check_supplies(struct power_supply *psy)
return power_supply_populate_supplied_from(psy);
}
#else
static inline int power_supply_check_supplies(struct power_supply *psy)
static int power_supply_check_supplies(struct power_supply *psy)
{
int nval, ret;
if (!psy->dev.parent)
return 0;
nval = device_property_read_string_array(psy->dev.parent,
"supplied-from", NULL, 0);
if (nval <= 0)
return 0;
psy->supplied_from = devm_kmalloc_array(&psy->dev, nval,
sizeof(char *), GFP_KERNEL);
if (!psy->supplied_from)
return -ENOMEM;
ret = device_property_read_string_array(psy->dev.parent,
"supplied-from", (const char **)psy->supplied_from, nval);
if (ret < 0)
return ret;
psy->num_supplies = nval;
return 0;
}
#endif
@@ -497,6 +520,62 @@ struct power_supply *devm_power_supply_get_by_phandle(struct device *dev,
EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle);
#endif /* CONFIG_OF */
int power_supply_get_battery_info(struct power_supply *psy,
struct power_supply_battery_info *info)
{
struct device_node *battery_np;
const char *value;
int err;
info->energy_full_design_uwh = -EINVAL;
info->charge_full_design_uah = -EINVAL;
info->voltage_min_design_uv = -EINVAL;
info->precharge_current_ua = -EINVAL;
info->charge_term_current_ua = -EINVAL;
info->constant_charge_current_max_ua = -EINVAL;
info->constant_charge_voltage_max_uv = -EINVAL;
if (!psy->of_node) {
dev_warn(&psy->dev, "%s currently only supports devicetree\n",
__func__);
return -ENXIO;
}
battery_np = of_parse_phandle(psy->of_node, "monitored-battery", 0);
if (!battery_np)
return -ENODEV;
err = of_property_read_string(battery_np, "compatible", &value);
if (err)
return err;
if (strcmp("simple-battery", value))
return -ENODEV;
/* The property and field names below must correspond to elements
* in enum power_supply_property. For reasoning, see
* Documentation/power/power_supply_class.txt.
*/
of_property_read_u32(battery_np, "energy-full-design-microwatt-hours",
&info->energy_full_design_uwh);
of_property_read_u32(battery_np, "charge-full-design-microamp-hours",
&info->charge_full_design_uah);
of_property_read_u32(battery_np, "voltage-min-design-microvolt",
&info->voltage_min_design_uv);
of_property_read_u32(battery_np, "precharge-current-microamp",
&info->precharge_current_ua);
of_property_read_u32(battery_np, "charge-term-current-microamp",
&info->charge_term_current_ua);
of_property_read_u32(battery_np, "constant_charge_current_max_microamp",
&info->constant_charge_current_max_ua);
of_property_read_u32(battery_np, "constant_charge_voltage_max_microvolt",
&info->constant_charge_voltage_max_uv);
return 0;
}
EXPORT_SYMBOL_GPL(power_supply_get_battery_info);
int power_supply_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
@@ -669,7 +748,7 @@ static int ps_set_cur_charge_cntl_limit(struct thermal_cooling_device *tcd,
return ret;
}
static struct thermal_cooling_device_ops psy_tcd_ops = {
static const struct thermal_cooling_device_ops psy_tcd_ops = {
.get_max_state = ps_get_max_charge_cntl_limit,
.get_cur_state = ps_get_cur_chrage_cntl_limit,
.set_cur_state = ps_set_cur_charge_cntl_limit,

View File

@@ -40,35 +40,42 @@
static struct device_attribute power_supply_attrs[];
static const char * const power_supply_type_text[] = {
"Unknown", "Battery", "UPS", "Mains", "USB",
"USB_DCP", "USB_CDP", "USB_ACA", "USB_C",
"USB_PD", "USB_PD_DRP", "BrickID"
};
static const char * const power_supply_status_text[] = {
"Unknown", "Charging", "Discharging", "Not charging", "Full"
};
static const char * const power_supply_charge_type_text[] = {
"Unknown", "N/A", "Trickle", "Fast"
};
static const char * const power_supply_health_text[] = {
"Unknown", "Good", "Overheat", "Dead", "Over voltage",
"Unspecified failure", "Cold", "Watchdog timer expire",
"Safety timer expire"
};
static const char * const power_supply_technology_text[] = {
"Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd",
"LiMn"
};
static const char * const power_supply_capacity_level_text[] = {
"Unknown", "Critical", "Low", "Normal", "High", "Full"
};
static const char * const power_supply_scope_text[] = {
"Unknown", "System", "Device"
};
static ssize_t power_supply_show_property(struct device *dev,
struct device_attribute *attr,
char *buf) {
static char *type_text[] = {
"Unknown", "Battery", "UPS", "Mains", "USB",
"USB_DCP", "USB_CDP", "USB_ACA", "USB_C",
"USB_PD", "USB_PD_DRP"
};
static char *status_text[] = {
"Unknown", "Charging", "Discharging", "Not charging", "Full"
};
static char *charge_type[] = {
"Unknown", "N/A", "Trickle", "Fast"
};
static char *health_text[] = {
"Unknown", "Good", "Overheat", "Dead", "Over voltage",
"Unspecified failure", "Cold", "Watchdog timer expire",
"Safety timer expire"
};
static char *technology_text[] = {
"Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd",
"LiMn"
};
static char *capacity_level_text[] = {
"Unknown", "Critical", "Low", "Normal", "High", "Full"
};
static char *scope_text[] = {
"Unknown", "System", "Device"
};
ssize_t ret = 0;
struct power_supply *psy = dev_get_drvdata(dev);
const ptrdiff_t off = attr - power_supply_attrs;
@@ -91,19 +98,26 @@ static ssize_t power_supply_show_property(struct device *dev,
}
if (off == POWER_SUPPLY_PROP_STATUS)
return sprintf(buf, "%s\n", status_text[value.intval]);
return sprintf(buf, "%s\n",
power_supply_status_text[value.intval]);
else if (off == POWER_SUPPLY_PROP_CHARGE_TYPE)
return sprintf(buf, "%s\n", charge_type[value.intval]);
return sprintf(buf, "%s\n",
power_supply_charge_type_text[value.intval]);
else if (off == POWER_SUPPLY_PROP_HEALTH)
return sprintf(buf, "%s\n", health_text[value.intval]);
return sprintf(buf, "%s\n",
power_supply_health_text[value.intval]);
else if (off == POWER_SUPPLY_PROP_TECHNOLOGY)
return sprintf(buf, "%s\n", technology_text[value.intval]);
return sprintf(buf, "%s\n",
power_supply_technology_text[value.intval]);
else if (off == POWER_SUPPLY_PROP_CAPACITY_LEVEL)
return sprintf(buf, "%s\n", capacity_level_text[value.intval]);
return sprintf(buf, "%s\n",
power_supply_capacity_level_text[value.intval]);
else if (off == POWER_SUPPLY_PROP_TYPE)
return sprintf(buf, "%s\n", type_text[value.intval]);
return sprintf(buf, "%s\n",
power_supply_type_text[value.intval]);
else if (off == POWER_SUPPLY_PROP_SCOPE)
return sprintf(buf, "%s\n", scope_text[value.intval]);
return sprintf(buf, "%s\n",
power_supply_scope_text[value.intval]);
else if (off >= POWER_SUPPLY_PROP_MODEL_NAME)
return sprintf(buf, "%s\n", value.strval);
@@ -117,14 +131,46 @@ static ssize_t power_supply_store_property(struct device *dev,
struct power_supply *psy = dev_get_drvdata(dev);
const ptrdiff_t off = attr - power_supply_attrs;
union power_supply_propval value;
long long_val;
/* TODO: support other types than int */
ret = kstrtol(buf, 10, &long_val);
if (ret < 0)
return ret;
/* maybe it is a enum property? */
switch (off) {
case POWER_SUPPLY_PROP_STATUS:
ret = sysfs_match_string(power_supply_status_text, buf);
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
ret = sysfs_match_string(power_supply_charge_type_text, buf);
break;
case POWER_SUPPLY_PROP_HEALTH:
ret = sysfs_match_string(power_supply_health_text, buf);
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
ret = sysfs_match_string(power_supply_technology_text, buf);
break;
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
ret = sysfs_match_string(power_supply_capacity_level_text, buf);
break;
case POWER_SUPPLY_PROP_SCOPE:
ret = sysfs_match_string(power_supply_scope_text, buf);
break;
default:
ret = -EINVAL;
}
value.intval = long_val;
/*
* If no match was found, then check to see if it is an integer.
* Integer values are valid for enums in addition to the text value.
*/
if (ret < 0) {
long long_val;
ret = kstrtol(buf, 10, &long_val);
if (ret < 0)
return ret;
ret = long_val;
}
value.intval = ret;
ret = power_supply_set_property(psy, off, &value);
if (ret < 0)
@@ -196,6 +242,7 @@ static struct device_attribute power_supply_attrs[] = {
POWER_SUPPLY_ATTR(time_to_full_avg),
POWER_SUPPLY_ATTR(type),
POWER_SUPPLY_ATTR(scope),
POWER_SUPPLY_ATTR(precharge_current),
POWER_SUPPLY_ATTR(charge_term_current),
POWER_SUPPLY_ATTR(calibrate),
/* Properties of type `const char *' */

View File

@@ -171,6 +171,7 @@ struct sbs_info {
u32 i2c_retry_count;
u32 poll_retry_count;
struct delayed_work work;
struct mutex mode_lock;
};
static char model_name[I2C_SMBUS_BLOCK_MAX + 1];
@@ -199,7 +200,7 @@ static int sbs_read_word_data(struct i2c_client *client, u8 address)
return ret;
}
return le16_to_cpu(ret);
return ret;
}
static int sbs_read_string_data(struct i2c_client *client, u8 address,
@@ -265,7 +266,7 @@ static int sbs_read_string_data(struct i2c_client *client, u8 address,
memcpy(values, block_buffer + 1, block_length);
values[block_length] = '\0';
return le16_to_cpu(ret);
return ret;
}
static int sbs_write_word_data(struct i2c_client *client, u8 address,
@@ -278,8 +279,7 @@ static int sbs_write_word_data(struct i2c_client *client, u8 address,
retries = chip->i2c_retry_count;
while (retries > 0) {
ret = i2c_smbus_write_word_data(client, address,
le16_to_cpu(value));
ret = i2c_smbus_write_word_data(client, address, value);
if (ret >= 0)
break;
retries--;
@@ -438,6 +438,11 @@ static int sbs_get_battery_property(struct i2c_client *client,
} else {
if (psp == POWER_SUPPLY_PROP_STATUS)
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
else if (psp == POWER_SUPPLY_PROP_CAPACITY)
/* sbs spec says that this can be >100 %
* even if max value is 100 %
*/
val->intval = min(ret, 100);
else
val->intval = 0;
}
@@ -548,12 +553,7 @@ static int sbs_get_battery_capacity(struct i2c_client *client,
if (ret < 0)
return ret;
if (psp == POWER_SUPPLY_PROP_CAPACITY) {
/* sbs spec says that this can be >100 %
* even if max value is 100 % */
val->intval = min(ret, 100);
} else
val->intval = ret;
val->intval = ret;
ret = sbs_set_battery_mode(client, mode);
if (ret < 0)
@@ -618,12 +618,17 @@ static int sbs_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_CHARGE_NOW:
case POWER_SUPPLY_PROP_CHARGE_FULL:
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
case POWER_SUPPLY_PROP_CAPACITY:
ret = sbs_get_property_index(client, psp);
if (ret < 0)
break;
/* sbs_get_battery_capacity() will change the battery mode
* temporarily to read the requested attribute. Ensure we stay
* in the desired mode for the duration of the attribute read.
*/
mutex_lock(&chip->mode_lock);
ret = sbs_get_battery_capacity(client, ret, psp, val);
mutex_unlock(&chip->mode_lock);
break;
case POWER_SUPPLY_PROP_SERIAL_NUMBER:
@@ -640,6 +645,7 @@ static int sbs_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG:
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
case POWER_SUPPLY_PROP_CAPACITY:
ret = sbs_get_property_index(client, psp);
if (ret < 0)
break;
@@ -808,6 +814,7 @@ static int sbs_probe(struct i2c_client *client,
psy_cfg.of_node = client->dev.of_node;
psy_cfg.drv_data = chip;
chip->last_state = POWER_SUPPLY_STATUS_UNKNOWN;
mutex_init(&chip->mode_lock);
/* use pdata if available, fall back to DT properties,
* or hardcoded defaults if not

View File

@@ -153,7 +153,7 @@ struct twl4030_bci {
};
/* strings for 'usb_mode' values */
static char *modes[] = { "off", "auto", "continuous" };
static const char *modes[] = { "off", "auto", "continuous" };
/*
* clear and set bits on an given register on a given module
@@ -624,63 +624,6 @@ static irqreturn_t twl4030_bci_interrupt(int irq, void *arg)
return IRQ_HANDLED;
}
/*
* Provide "max_current" attribute in sysfs.
*/
static ssize_t
twl4030_bci_max_current_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t n)
{
struct twl4030_bci *bci = dev_get_drvdata(dev->parent);
int cur = 0;
int status = 0;
status = kstrtoint(buf, 10, &cur);
if (status)
return status;
if (cur < 0)
return -EINVAL;
if (dev == &bci->ac->dev)
bci->ac_cur = cur;
else
bci->usb_cur_target = cur;
twl4030_charger_update_current(bci);
return n;
}
/*
* sysfs max_current show
*/
static ssize_t twl4030_bci_max_current_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int status = 0;
int cur = -1;
u8 bcictl1;
struct twl4030_bci *bci = dev_get_drvdata(dev->parent);
if (dev == &bci->ac->dev) {
if (!bci->ac_is_active)
cur = bci->ac_cur;
} else {
if (bci->ac_is_active)
cur = bci->usb_cur_target;
}
if (cur < 0) {
cur = twl4030bci_read_adc_val(TWL4030_BCIIREF1);
if (cur < 0)
return cur;
status = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1);
if (status < 0)
return status;
cur = regval2ua(cur, bcictl1 & TWL4030_CGAIN);
}
return scnprintf(buf, PAGE_SIZE, "%u\n", cur);
}
static DEVICE_ATTR(max_current, 0644, twl4030_bci_max_current_show,
twl4030_bci_max_current_store);
static void twl4030_bci_usb_work(struct work_struct *data)
{
struct twl4030_bci *bci = container_of(data, struct twl4030_bci, work);
@@ -726,14 +669,10 @@ twl4030_bci_mode_store(struct device *dev, struct device_attribute *attr,
int mode;
int status;
if (sysfs_streq(buf, modes[0]))
mode = 0;
else if (sysfs_streq(buf, modes[1]))
mode = 1;
else if (sysfs_streq(buf, modes[2]))
mode = 2;
else
return -EINVAL;
mode = sysfs_match_string(modes, buf);
if (mode < 0)
return mode;
if (dev == &bci->ac->dev) {
if (mode == 2)
return -EINVAL;
@@ -1041,6 +980,12 @@ static int twl4030_bci_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, bci);
bci->channel_vac = devm_iio_channel_get(&pdev->dev, "vac");
if (IS_ERR(bci->channel_vac)) {
bci->channel_vac = NULL;
dev_warn(&pdev->dev, "could not request vac iio channel");
}
bci->ac = devm_power_supply_register(&pdev->dev, &twl4030_bci_ac_desc,
NULL);
if (IS_ERR(bci->ac)) {
@@ -1074,12 +1019,6 @@ static int twl4030_bci_probe(struct platform_device *pdev)
return ret;
}
bci->channel_vac = iio_channel_get(&pdev->dev, "vac");
if (IS_ERR(bci->channel_vac)) {
bci->channel_vac = NULL;
dev_warn(&pdev->dev, "could not request vac iio channel");
}
INIT_WORK(&bci->work, twl4030_bci_usb_work);
INIT_DELAYED_WORK(&bci->current_worker, twl4030_current_worker);
@@ -1101,7 +1040,7 @@ static int twl4030_bci_probe(struct platform_device *pdev)
TWL4030_INTERRUPTS_BCIIMR1A);
if (ret < 0) {
dev_err(&pdev->dev, "failed to unmask interrupts: %d\n", ret);
goto fail;
return ret;
}
reg = ~(u32)(TWL4030_VBATOV | TWL4030_VBUSOV | TWL4030_ACCHGOV);
@@ -1111,14 +1050,10 @@ static int twl4030_bci_probe(struct platform_device *pdev)
dev_warn(&pdev->dev, "failed to unmask interrupts: %d\n", ret);
twl4030_charger_update_current(bci);
if (device_create_file(&bci->usb->dev, &dev_attr_max_current))
dev_warn(&pdev->dev, "could not create sysfs file\n");
if (device_create_file(&bci->usb->dev, &dev_attr_mode))
dev_warn(&pdev->dev, "could not create sysfs file\n");
if (device_create_file(&bci->ac->dev, &dev_attr_mode))
dev_warn(&pdev->dev, "could not create sysfs file\n");
if (device_create_file(&bci->ac->dev, &dev_attr_max_current))
dev_warn(&pdev->dev, "could not create sysfs file\n");
twl4030_charger_enable_ac(bci, true);
if (!IS_ERR_OR_NULL(bci->transceiver))
@@ -1134,10 +1069,6 @@ static int twl4030_bci_probe(struct platform_device *pdev)
twl4030_charger_enable_backup(0, 0);
return 0;
fail:
iio_channel_release(bci->channel_vac);
return ret;
}
static int twl4030_bci_remove(struct platform_device *pdev)
@@ -1148,11 +1079,7 @@ static int twl4030_bci_remove(struct platform_device *pdev)
twl4030_charger_enable_usb(bci, false);
twl4030_charger_enable_backup(0, 0);
iio_channel_release(bci->channel_vac);
device_remove_file(&bci->usb->dev, &dev_attr_max_current);
device_remove_file(&bci->usb->dev, &dev_attr_mode);
device_remove_file(&bci->ac->dev, &dev_attr_max_current);
device_remove_file(&bci->ac->dev, &dev_attr_mode);
/* mask interrupts */
twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff,