Commit 7f2ebde7 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'for-v4.12' 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:
   - gemini-poweroff
   - cpcap-charger (for Motorola Droid 4)
   - battery-lego-ev3 (for LEGO Mindstorms EV3)

  New chip/feature support:
   - bq24190-charger: add runtime PM support
   - bq24190-charger: add bq24192i support
   - register masking for syscon-poweroff

  ... and misc small fixes & cleanups

* tag 'for-v4.12' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (29 commits)
  power: supply: bq24190_charger: Use new extcon_register_notifier_all()
  power: supply: bq24190_charger: Longer delay while polling reset flag
  power: supply: bq24190_charger: Uniform pm_runtime_get() failure handling
  power: supply: bq24190_charger: Clean up extcon code
  power: supply: bq24190_charger: Limit over/under voltage fault logging
  power: supply: New driver for LEGO MINDSTORMS EV3 battery
  dt-bindings: power: supply: New bindings for LEGO MINDSTORMS EV3 battery
  power: supply: tps65217: remove debug messages for function calls
  power: supply: ltc2941-battery-gauge: Add OF device ID table
  power: supply: ltc2941-battery-gauge: Add vendor to compatibles in binding
  power: supply: charger-manager: simplify return statements
  power: supply: lp8788: prevent out of bounds array access
  power: supply: cpcap-charger: Add minimal CPCAP PMIC battery charger
  power: supply: bq24190_charger: Use extcon to determine ilimit, 5v boost
  power: supply: bq24190_charger: Add support for bq24192i
  power: supply: bq24190_charger: Use i2c-core irq-mapping code
  power: bq24190_charger: mark PM functions as __maybe_unused
  power: supply: sbs-charger: simplified bool function
  power: supply: ab8500: Replaced spaces with tabs in indent
  power: supply: bq25890: Use gpiod_get()
  ...
parents cdbfbba9 6c381663
* Device-Tree bindings for Cortina Systems Gemini Poweroff
This is a special IP block in the Cortina Gemini SoC that only
deals with different ways to power the system down.
Required properties:
- compatible: should be "cortina,gemini-power-controller"
- reg: should contain the physical memory base and size
- interrupts: should contain the power management interrupt
Example:
power-controller@4b000000 {
compatible = "cortina,gemini-power-controller";
reg = <0x4b000000 0x100>;
interrupts = <26 IRQ_TYPE_EDGE_FALLING>;
};
......@@ -3,13 +3,20 @@ Generic SYSCON mapped register poweroff driver
This is a generic poweroff driver using syscon to map the poweroff register.
The poweroff is generally performed with a write to the poweroff register
defined by the register map pointed by syscon reference plus the offset
with the mask defined in the poweroff node.
with the value and mask defined in the poweroff node.
Required properties:
- compatible: should contain "syscon-poweroff"
- regmap: this is phandle to the register map node
- offset: offset in the register map for the poweroff register (in bytes)
- mask: the poweroff value written to the poweroff register (32 bit access)
- value: the poweroff value written to the poweroff register (32 bit access)
Optional properties:
- mask: update only the register bits defined by the mask (32 bit)
Legacy usage:
If a node doesn't contain a value property but contains a mask property, the
mask property is used as the value.
Default will be little endian mode, 32 bit access only.
......
Motorola CPCAP PMIC battery charger binding
Required properties:
- compatible: Shall be "motorola,mapphone-cpcap-charger"
- interrupts: Interrupt specifier for each name in interrupt-names
- interrupt-names: Should contain the following entries:
"chrg_det", "rvrs_chrg", "chrg_se1b", "se0conn",
"rvrs_mode", "chrgcurr1", "vbusvld", "battdetb"
- io-channels: IIO ADC channel specifier for each name in io-channel-names
- io-channel-names: Should contain the following entries:
"battdetb", "battp", "vbus", "chg_isense", "batti"
Optional properties:
- mode-gpios: Optionally CPCAP charger can have a companion wireless
charge controller that is controlled with two GPIOs
that are active low.
Example:
cpcap_charger: charger {
compatible = "motorola,mapphone-cpcap-charger";
interrupts-extended = <
&cpcap 13 0 &cpcap 12 0 &cpcap 29 0 &cpcap 28 0
&cpcap 22 0 &cpcap 20 0 &cpcap 19 0 &cpcap 54 0
>;
interrupt-names =
"chrg_det", "rvrs_chrg", "chrg_se1b", "se0conn",
"rvrs_mode", "chrgcurr1", "vbusvld", "battdetb";
mode-gpios = <&gpio3 29 GPIO_ACTIVE_LOW
&gpio3 23 GPIO_ACTIVE_LOW>;
io-channels = <&cpcap_adc 0 &cpcap_adc 1
&cpcap_adc 2 &cpcap_adc 5
&cpcap_adc 6>;
io-channel-names = "battdetb", "battp",
"vbus", "chg_isense",
"batti";
};
LEGO MINDSTORMS EV3 Battery
~~~~~~~~~~~~~~~~~~~~~~~~~~~
LEGO MINDSTORMS EV3 has some built-in capability for monitoring the battery.
It uses 6 AA batteries or a special Li-ion rechargeable battery pack that is
detected by a key switch in the battery compartment.
Required properties:
- compatible: Must be "lego,ev3-battery"
- io-channels: phandles to analog inputs for reading voltage and current
- io-channel-names: Must be "voltage", "current"
- rechargeable-gpios: phandle to the rechargeable battery indication gpio
Example:
battery {
compatible = "lego,ev3-battery";
io-channels = <&adc 4>, <&adc 3>;
io-channel-names = "voltage", "current";
rechargeable-gpios = <&gpio 136 GPIO_ACTIVE_LOW>;
};
......@@ -6,8 +6,8 @@ temperature monitoring, and uses a slightly different conversion
formula for the charge counter.
Required properties:
- compatible: Should contain "ltc2941" or "ltc2943" which also indicates the
type of I2C chip attached.
- compatible: Should contain "lltc,ltc2941" or "lltc,ltc2943" which also
indicates the type of I2C chip attached.
- reg: The 7-bit I2C address.
- lltc,resistor-sense: The sense resistor value in milli-ohms. Can be a 32-bit
negative value when the battery has been connected to the wrong end of the
......@@ -20,7 +20,7 @@ Required properties:
Example from the Topic Miami Florida board:
fuelgauge: ltc2943@64 {
compatible = "ltc2943";
compatible = "lltc,ltc2943";
reg = <0x64>;
lltc,resistor-sense = <15>;
lltc,prescaler-exponent = <5>; /* 2^(2*5) = 1024 */
......
......@@ -50,6 +50,13 @@ static void devm_extcon_dev_notifier_unreg(struct device *dev, void *res)
extcon_unregister_notifier(this->edev, this->id, this->nb);
}
static void devm_extcon_dev_notifier_all_unreg(struct device *dev, void *res)
{
struct extcon_dev_notifier_devres *this = res;
extcon_unregister_notifier_all(this->edev, this->nb);
}
/**
* devm_extcon_dev_allocate - Allocate managed extcon device
* @dev: device owning the extcon device being created
......@@ -214,3 +221,57 @@ void devm_extcon_unregister_notifier(struct device *dev,
devm_extcon_dev_match, edev));
}
EXPORT_SYMBOL(devm_extcon_unregister_notifier);
/**
* devm_extcon_register_notifier_all()
* - Resource-managed extcon_register_notifier_all()
* @dev: device to allocate extcon device
* @edev: the extcon device that has the external connecotr.
* @nb: a notifier block to be registered.
*
* This function manages automatically the notifier of extcon device using
* device resource management and simplify the control of unregistering
* the notifier of extcon device. To get more information, refer that function.
*
* Returns 0 if success or negaive error number if failure.
*/
int devm_extcon_register_notifier_all(struct device *dev, struct extcon_dev *edev,
struct notifier_block *nb)
{
struct extcon_dev_notifier_devres *ptr;
int ret;
ptr = devres_alloc(devm_extcon_dev_notifier_all_unreg, sizeof(*ptr),
GFP_KERNEL);
if (!ptr)
return -ENOMEM;
ret = extcon_register_notifier_all(edev, nb);
if (ret) {
devres_free(ptr);
return ret;
}
ptr->edev = edev;
ptr->nb = nb;
devres_add(dev, ptr);
return 0;
}
EXPORT_SYMBOL(devm_extcon_register_notifier_all);
/**
* devm_extcon_unregister_notifier_all()
* - Resource-managed extcon_unregister_notifier_all()
* @dev: device to allocate extcon device
* @edev: the extcon device that has the external connecotr.
* @nb: a notifier block to be registered.
*/
void devm_extcon_unregister_notifier_all(struct device *dev,
struct extcon_dev *edev,
struct notifier_block *nb)
{
WARN_ON(devres_release(dev, devm_extcon_dev_notifier_all_unreg,
devm_extcon_dev_match, edev));
}
EXPORT_SYMBOL(devm_extcon_unregister_notifier_all);
......@@ -448,8 +448,19 @@ int extcon_sync(struct extcon_dev *edev, unsigned int id)
spin_lock_irqsave(&edev->lock, flags);
state = !!(edev->state & BIT(index));
/*
* Call functions in a raw notifier chain for the specific one
* external connector.
*/
raw_notifier_call_chain(&edev->nh[index], state, edev);
/*
* Call functions in a raw notifier chain for the all supported
* external connectors.
*/
raw_notifier_call_chain(&edev->nh_all, state, edev);
/* This could be in interrupt handler */
prop_buf = (char *)get_zeroed_page(GFP_ATOMIC);
if (!prop_buf) {
......@@ -954,6 +965,59 @@ int extcon_unregister_notifier(struct extcon_dev *edev, unsigned int id,
}
EXPORT_SYMBOL_GPL(extcon_unregister_notifier);
/**
* extcon_register_notifier_all() - Register a notifier block for all connectors
* @edev: the extcon device that has the external connecotr.
* @nb: a notifier block to be registered.
*
* This fucntion registers a notifier block in order to receive the state
* change of all supported external connectors from extcon device.
* And The second parameter given to the callback of nb (val) is
* the current state and third parameter is the edev pointer.
*
* Returns 0 if success or error number if fail
*/
int extcon_register_notifier_all(struct extcon_dev *edev,
struct notifier_block *nb)
{
unsigned long flags;
int ret;
if (!edev || !nb)
return -EINVAL;
spin_lock_irqsave(&edev->lock, flags);
ret = raw_notifier_chain_register(&edev->nh_all, nb);
spin_unlock_irqrestore(&edev->lock, flags);
return ret;
}
EXPORT_SYMBOL_GPL(extcon_register_notifier_all);
/**
* extcon_unregister_notifier_all() - Unregister a notifier block from extcon.
* @edev: the extcon device that has the external connecotr.
* @nb: a notifier block to be registered.
*
* Returns 0 if success or error number if fail
*/
int extcon_unregister_notifier_all(struct extcon_dev *edev,
struct notifier_block *nb)
{
unsigned long flags;
int ret;
if (!edev || !nb)
return -EINVAL;
spin_lock_irqsave(&edev->lock, flags);
ret = raw_notifier_chain_unregister(&edev->nh_all, nb);
spin_unlock_irqrestore(&edev->lock, flags);
return ret;
}
EXPORT_SYMBOL_GPL(extcon_unregister_notifier_all);
static struct attribute *extcon_attrs[] = {
&dev_attr_state.attr,
&dev_attr_name.attr,
......@@ -1212,6 +1276,8 @@ int extcon_dev_register(struct extcon_dev *edev)
for (index = 0; index < edev->max_supported; index++)
RAW_INIT_NOTIFIER_HEAD(&edev->nh[index]);
RAW_INIT_NOTIFIER_HEAD(&edev->nh_all);
dev_set_drvdata(&edev->dev, edev);
edev->state = 0;
......
......@@ -21,6 +21,8 @@
* @dev: Device of this extcon.
* @state: Attach/detach state of this extcon. Do not provide at
* register-time.
* @nh_all: Notifier for the state change events for all supported
* external connectors from this extcon.
* @nh: Notifier for the state change events from this extcon
* @entry: To support list of extcon devices so that users can
* search for extcon devices based on the extcon name.
......@@ -43,6 +45,7 @@ struct extcon_dev {
/* Internal data. Please do not set. */
struct device dev;
struct raw_notifier_head nh_all;
struct raw_notifier_head *nh;
struct list_head entry;
int max_supported;
......
......@@ -67,6 +67,15 @@ config POWER_RESET_BRCMSTB
Say Y here if you have a Broadcom STB board and you wish
to have restart support.
config POWER_RESET_GEMINI_POWEROFF
bool "Cortina Gemini power-off driver"
depends on ARCH_GEMINI || COMPILE_TEST
depends on OF && HAS_IOMEM
default ARCH_GEMINI
help
This driver supports turning off the Cortina Gemini SoC.
Select this if you're building a kernel with Gemini SoC support.
config POWER_RESET_GPIO
bool "GPIO power-off driver"
depends on OF_GPIO
......
......@@ -5,6 +5,7 @@ obj-$(CONFIG_POWER_RESET_AT91_SAMA5D2_SHDWC) += at91-sama5d2_shdwc.o
obj-$(CONFIG_POWER_RESET_AXXIA) += axxia-reset.o
obj-$(CONFIG_POWER_RESET_BRCMKONA) += brcm-kona-reset.o
obj-$(CONFIG_POWER_RESET_BRCMSTB) += brcmstb-reboot.o
obj-$(CONFIG_POWER_RESET_GEMINI_POWEROFF) += gemini-poweroff.o
obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o
obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o
obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o
......
/*
* Gemini power management controller
* Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
*
* Inspired by code from the SL3516 board support by Jason Lee
* Inspired by code from Janos Laube <janos.dev@gmail.com>
*/
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/bitops.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/reboot.h>
#define GEMINI_PWC_ID 0x00010500
#define GEMINI_PWC_IDREG 0x00
#define GEMINI_PWC_CTRLREG 0x04
#define GEMINI_PWC_STATREG 0x08
#define GEMINI_CTRL_SHUTDOWN BIT(0)
#define GEMINI_CTRL_ENABLE BIT(1)
#define GEMINI_CTRL_IRQ_CLR BIT(2)
#define GEMINI_STAT_CIR BIT(4)
#define GEMINI_STAT_RTC BIT(5)
#define GEMINI_STAT_POWERBUTTON BIT(6)
struct gemini_powercon {
struct device *dev;
void __iomem *base;
};
static irqreturn_t gemini_powerbutton_interrupt(int irq, void *data)
{
struct gemini_powercon *gpw = data;
u32 val;
/* ACK the IRQ */
val = readl(gpw->base + GEMINI_PWC_CTRLREG);
val |= GEMINI_CTRL_IRQ_CLR;
writel(val, gpw->base + GEMINI_PWC_CTRLREG);
val = readl(gpw->base + GEMINI_PWC_STATREG);
val &= 0x70U;
switch (val) {
case GEMINI_STAT_CIR:
dev_info(gpw->dev, "infrared poweroff\n");
orderly_poweroff(true);
break;
case GEMINI_STAT_RTC:
dev_info(gpw->dev, "RTC poweroff\n");
orderly_poweroff(true);
break;
case GEMINI_STAT_POWERBUTTON:
dev_info(gpw->dev, "poweroff button pressed\n");
orderly_poweroff(true);
break;
default:
dev_info(gpw->dev, "other power management IRQ\n");
break;
}
return IRQ_HANDLED;
}
/* This callback needs this static local as it has void as argument */
static struct gemini_powercon *gpw_poweroff;
static void gemini_poweroff(void)
{
struct gemini_powercon *gpw = gpw_poweroff;
u32 val;
dev_crit(gpw->dev, "Gemini power off\n");
val = readl(gpw->base + GEMINI_PWC_CTRLREG);
val |= GEMINI_CTRL_ENABLE | GEMINI_CTRL_IRQ_CLR;
writel(val, gpw->base + GEMINI_PWC_CTRLREG);
val &= ~GEMINI_CTRL_ENABLE;
val |= GEMINI_CTRL_SHUTDOWN;
writel(val, gpw->base + GEMINI_PWC_CTRLREG);
}
static int gemini_poweroff_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *res;
struct gemini_powercon *gpw;
u32 val;
int irq;
int ret;
gpw = devm_kzalloc(dev, sizeof(*gpw), GFP_KERNEL);
if (!gpw)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
gpw->base = devm_ioremap_resource(dev, res);
if (IS_ERR(gpw->base))
return PTR_ERR(gpw->base);
irq = platform_get_irq(pdev, 0);
if (!irq)
return -EINVAL;
gpw->dev = dev;
val = readl(gpw->base + GEMINI_PWC_IDREG);
val &= 0xFFFFFF00U;
if (val != GEMINI_PWC_ID) {
dev_err(dev, "wrong power controller ID: %08x\n",
val);
return -ENODEV;
}
/* Clear the power management IRQ */
val = readl(gpw->base + GEMINI_PWC_CTRLREG);
val |= GEMINI_CTRL_IRQ_CLR;
writel(val, gpw->base + GEMINI_PWC_CTRLREG);
ret = devm_request_irq(dev, irq, gemini_powerbutton_interrupt, 0,
"poweroff", gpw);
if (ret)
return ret;
pm_power_off = gemini_poweroff;
gpw_poweroff = gpw;
/*
* Enable the power controller. This is crucial on Gemini
* systems: if this is not done, pressing the power button
* will result in unconditional poweroff without any warning.
* This makes the kernel handle the poweroff.
*/
val = readl(gpw->base + GEMINI_PWC_CTRLREG);
val |= GEMINI_CTRL_ENABLE;
writel(val, gpw->base + GEMINI_PWC_CTRLREG);
dev_info(dev, "Gemini poweroff driver registered\n");
return 0;
}
static const struct of_device_id gemini_poweroff_of_match[] = {
{
.compatible = "cortina,gemini-power-controller",
},
{}
};
static struct platform_driver gemini_poweroff_driver = {
.probe = gemini_poweroff_probe,
.driver = {
.name = "gemini-poweroff",
.of_match_table = gemini_poweroff_of_match,
},
};
builtin_platform_driver(gemini_poweroff_driver);
......@@ -28,12 +28,13 @@
static struct regmap *map;
static u32 offset;
static u32 value;
static u32 mask;
static void syscon_poweroff(void)
{
/* Issue the poweroff */
regmap_write(map, offset, mask);
regmap_update_bits(map, offset, mask, value);
mdelay(1000);
......@@ -43,6 +44,7 @@ static void syscon_poweroff(void)
static int syscon_poweroff_probe(struct platform_device *pdev)
{
char symname[KSYM_NAME_LEN];
int mask_err, value_err;
map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "regmap");
if (IS_ERR(map)) {
......@@ -55,11 +57,22 @@ static int syscon_poweroff_probe(struct platform_device *pdev)
return -EINVAL;
}
if (of_property_read_u32(pdev->dev.of_node, "mask", &mask)) {
dev_err(&pdev->dev, "unable to read 'mask'");
value_err = of_property_read_u32(pdev->dev.of_node, "value", &value);
mask_err = of_property_read_u32(pdev->dev.of_node, "mask", &mask);
if (value_err && mask_err) {
dev_err(&pdev->dev, "unable to read 'value' and 'mask'");
return -EINVAL;
}
if (value_err) {
/* support old binding */
value = mask;
mask = 0xFFFFFFFF;
} else if (mask_err) {
/* support value without mask*/
mask = 0xFFFFFFFF;
}
if (pm_power_off) {
lookup_symbol_name((ulong)pm_power_off, symname);
dev_err(&pdev->dev,
......
......@@ -117,6 +117,12 @@ config BATTERY_DS2782
Say Y here to enable support for the DS2782/DS2786 standalone battery
gas-gauge.
config BATTERY_LEGO_EV3
tristate "LEGO MINDSTORMS EV3 battery"
depends on OF && IIO && GPIOLIB
help
Say Y here to enable support for the LEGO MINDSTORMS EV3 battery.
config BATTERY_PMU
tristate "Apple PMU battery"
depends on PPC32 && ADB_PMU
......@@ -317,6 +323,14 @@ config BATTERY_RX51
Say Y here to enable support for battery information on Nokia
RX-51, also known as N900 tablet.
config CHARGER_CPCAP
tristate "CPCAP PMIC Charger Driver"
depends on MFD_CPCAP && IIO
default MFD_CPCAP
help
Say Y to enable support for CPCAP PMIC charger driver for Motorola
mobile devices such as Droid 4.
config CHARGER_ISP1704
tristate "ISP1704 USB Charger Detection"
depends on USB_PHY
......@@ -438,6 +452,7 @@ config CHARGER_BQ2415X
config CHARGER_BQ24190
tristate "TI BQ24190 battery charger driver"
depends on I2C
depends on EXTCON
depends on GPIOLIB || COMPILE_TEST
help
Say Y to enable support for the TI BQ24190 battery charger.
......
......@@ -25,6 +25,7 @@ obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o
obj-$(CONFIG_BATTERY_DS2782) += ds2782_battery.o
obj-$(CONFIG_BATTERY_GAUGE_LTC2941) += ltc2941-battery-gauge.o
obj-$(CONFIG_BATTERY_GOLDFISH) += goldfish_battery.o
obj-$(CONFIG_BATTERY_LEGO_EV3) += lego_ev3_battery.o
obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o
obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o
obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o
......@@ -51,6 +52,7 @@ obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o
obj-$(CONFIG_BATTERY_RX51) += rx51_battery.o
obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o abx500_chargalg.o pm2301_charger.o
obj-$(CONFIG_CHARGER_CPCAP) += cpcap-charger.o
obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o
obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o
obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o
......
......@@ -11,16 +11,15 @@
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/extcon.h>
#include <linux/of_irq.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <linux/power_supply.h>
#include <linux/workqueue.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 */
......@@ -39,6 +38,9 @@
#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_CHG_CONFIG_DISABLE 0x0
#define BQ24190_REG_POC_CHG_CONFIG_CHARGE 0x1
#define BQ24190_REG_POC_CHG_CONFIG_OTG 0x2
#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)
......@@ -151,10 +153,12 @@ struct bq24190_dev_info {
struct device *dev;
struct power_supply *charger;
struct power_supply *battery;
struct extcon_dev *extcon;
struct notifier_block extcon_nb;
struct delayed_work extcon_work;
char model_name[I2C_NAME_SIZE];
kernel_ulong_t model;
unsigned int gpio_int;
unsigned int irq;
bool initialized;
bool irq_event;
struct mutex f_reg_lock;
u8 f_reg;
u8 ss_reg;
......@@ -168,6 +172,12 @@ struct bq24190_dev_info {
* number at that index in the array is the real-world value that it
* represents.
*/
/* REG00[2:0] (IINLIM) in uAh */
static const int bq24190_isc_iinlim_values[] = {
100000, 150000, 500000, 900000, 1200000, 1500000, 2000000, 3000000
};
/* REG02[7:2] (ICHG) in uAh */
static const int bq24190_ccc_ichg_values[] = {
512000, 576000, 640000, 704000, 768000, 832000, 896000, 960000,
......@@ -418,6 +428,7 @@ static ssize_t bq24190_sysfs_show(struct device *dev,
struct power_supply *psy = dev_get_drvdata(dev);
struct bq24190_dev_info *bdi = power_supply_get_drvdata(psy);
struct bq24190_sysfs_field_info *info;
ssize_t count;
int ret;
u8 v;
......@@ -425,11 +436,20 @@ static ssize_t bq24190_sysfs_show(struct device *dev,
if (!info)
return -EINVAL;
ret = pm_runtime_get_sync(bdi->dev);
if (ret < 0)
return ret;
ret = bq24190_read_mask(bdi, info->reg, info->mask, info->shift, &v);
if (ret)
return ret;
count = ret;
else
count = scnprintf(buf, PAGE_SIZE, "%hhx\n", v);
return scnprintf(buf, PAGE_SIZE, "%hhx\n", v);
pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
return count;
}
static ssize_t bq24190_sysfs_store(struct device *dev,
......@@ -449,9 +469,16 @@ static ssize_t bq24190_sysfs_store(struct device *dev,
if (ret < 0)
return ret;
ret = pm_runtime_get_sync(bdi->dev);
if (ret < 0)
return ret;
ret = bq24190_write_mask(bdi, info->reg, info->mask, info->shift, v);
if (ret)
return ret;
count = ret;
pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
return count;
}
......@@ -523,16 +550,13 @@ static int bq24190_register_reset(struct bq24190_dev_info *bdi)
if (ret < 0)
return ret;
if (!v)
break;
if (v == 0)
return 0;
udelay(10);
usleep_range(100, 200);
} while (--limit);
if (!limit)
return -EIO;
return 0;
}
/* Charger power supply property routines */
......@@ -793,7 +817,9 @@ static int bq24190_charger_get_property(struct power_supply *psy,
dev_dbg(bdi->dev, "prop: %d\n", psp);
pm_runtime_get_sync(bdi->dev);
ret = pm_runtime_get_sync(bdi->dev);
if (ret < 0)
return ret;
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_TYPE:
......@@ -833,7 +859,9 @@ static int bq24190_charger_get_property(struct power_supply *psy,
ret = -ENODATA;
}
pm_runtime_put_sync(bdi->dev);
pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
return ret;
}
......@@ -846,7 +874,9 @@ static int bq24190_charger_set_property(struct power_supply *psy,
dev_dbg(bdi->dev, "prop: %d\n", psp);
pm_runtime_get_sync(bdi->dev);
ret = pm_runtime_get_sync(bdi->dev);
if (ret < 0)
return ret;
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_TYPE:
......@@ -862,7 +892,9 @@ static int bq24190_charger_set_property(struct power_supply *psy,
ret = -EINVAL;
}
pm_runtime_put_sync(bdi->dev);
pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
return ret;
}
......@@ -1063,7 +1095,9 @@ static int bq24190_battery_get_property(struct power_supply *psy,
dev_dbg(bdi->dev, "prop: %d\n", psp);
pm_runtime_get_sync(bdi->dev);
ret = pm_runtime_get_sync(bdi->dev);
if (ret < 0)
return ret;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
......@@ -1091,7 +1125,9 @@ static int bq24190_battery_get_property(struct power_supply *psy,
ret = -ENODATA;
}
pm_runtime_put_sync(bdi->dev);
pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
return ret;
}
......@@ -1104,7 +1140,9 @@ static int bq24190_battery_set_property(struct power_supply *psy,
dev_dbg(bdi->dev, "prop: %d\n", psp);
pm_runtime_get_sync(bdi->dev);
ret = pm_runtime_get_sync(bdi->dev);
if (ret < 0)
return ret;
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
......@@ -1117,7 +1155,9 @@ static int bq24190_battery_set_property(struct power_supply *psy,
ret = -EINVAL;
}
pm_runtime_put_sync(bdi->dev);
pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
return ret;
}
......@@ -1157,9 +1197,8 @@ static const struct power_supply_desc bq24190_battery_desc = {
.property_is_writeable = bq24190_battery_property_is_writeable,
};
static irqreturn_t bq24190_irq_handler_thread(int irq, void *data)
static void bq24190_check_status(struct bq24190_dev_info *bdi)
{
struct bq24190_dev_info *bdi = data;
const u8 battery_mask_ss = BQ24190_REG_SS_CHRG_STAT_MASK;
const u8 battery_mask_f = BQ24190_REG_F_BAT_FAULT_MASK
| BQ24190_REG_F_NTC_FAULT_MASK;
......@@ -1167,12 +1206,10 @@ static irqreturn_t bq24190_irq_handler_thread(int irq, void *data)
u8 ss_reg = 0, f_reg = 0;
int i, 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;
return;
}
i = 0;
......@@ -1180,12 +1217,17 @@ static irqreturn_t bq24190_irq_handler_thread(int irq, void *data)
ret = bq24190_read(bdi, BQ24190_REG_F, &f_reg);
if (ret < 0) {
dev_err(bdi->dev, "Can't read F reg: %d\n", ret);
goto out;
return;
}
} while (f_reg && ++i < 2);
/* ignore over/under voltage fault after disconnect */
if (f_reg == (1 << BQ24190_REG_F_CHRG_FAULT_SHIFT) &&
!(ss_reg & BQ24190_REG_SS_PG_STAT_MASK))
f_reg = 0;
if (f_reg != bdi->f_reg) {
dev_info(bdi->dev,
dev_warn(bdi->dev,
"Fault: boost %d, charge %d, battery %d, ntc %d\n",
!!(f_reg & BQ24190_REG_F_BOOST_FAULT_MASK),
!!(f_reg & BQ24190_REG_F_CHRG_FAULT_MASK),
......@@ -1229,90 +1271,126 @@ static irqreturn_t bq24190_irq_handler_thread(int irq, void *data)
if (alert_battery)
power_supply_changed(bdi->battery);
out:
pm_runtime_put_sync(bdi->dev);
dev_dbg(bdi->dev, "ss_reg: 0x%02x, f_reg: 0x%02x\n", ss_reg, f_reg);
}
static irqreturn_t bq24190_irq_handler_thread(int irq, void *data)
{
struct bq24190_dev_info *bdi = data;
int error;
bdi->irq_event = true;
error = pm_runtime_get_sync(bdi->dev);
if (error < 0) {
dev_warn(bdi->dev, "pm_runtime_get failed: %i\n", error);
pm_runtime_put_noidle(bdi->dev);
return IRQ_NONE;
}
bq24190_check_status(bdi);
pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
bdi->irq_event = false;
return IRQ_HANDLED;
}
static int bq24190_hw_init(struct bq24190_dev_info *bdi)
static void bq24190_extcon_work(struct work_struct *work)
{
struct bq24190_dev_info *bdi =
container_of(work, struct bq24190_dev_info, extcon_work.work);
int error, iinlim = 0;
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;
error = pm_runtime_get_sync(bdi->dev);
if (error < 0) {
dev_warn(bdi->dev, "pm_runtime_get failed: %i\n", error);
pm_runtime_put_noidle(bdi->dev);
return;
}
if (v != bdi->model) {
ret = -ENODEV;
goto out;
if (extcon_get_state(bdi->extcon, EXTCON_CHG_USB_SDP) == 1)
iinlim = 500000;
else if (extcon_get_state(bdi->extcon, EXTCON_CHG_USB_CDP) == 1 ||
extcon_get_state(bdi->extcon, EXTCON_CHG_USB_ACA) == 1)
iinlim = 1500000;
else if (extcon_get_state(bdi->extcon, EXTCON_CHG_USB_DCP) == 1)
iinlim = 2000000;
if (iinlim) {
error = bq24190_set_field_val(bdi, BQ24190_REG_ISC,
BQ24190_REG_ISC_IINLIM_MASK,
BQ24190_REG_ISC_IINLIM_SHIFT,
bq24190_isc_iinlim_values,
ARRAY_SIZE(bq24190_isc_iinlim_values),
iinlim);
if (error < 0)
dev_err(bdi->dev, "Can't set IINLIM: %d\n", error);
}
ret = bq24190_register_reset(bdi);
if (ret < 0)
goto out;
/* if no charger found and in USB host mode, set OTG 5V boost, else normal */
if (!iinlim && extcon_get_state(bdi->extcon, EXTCON_USB_HOST) == 1)
v = BQ24190_REG_POC_CHG_CONFIG_OTG;
else
v = BQ24190_REG_POC_CHG_CONFIG_CHARGE;
ret = bq24190_set_mode_host(bdi);
if (ret < 0)
goto out;
error = bq24190_write_mask(bdi, BQ24190_REG_POC,
BQ24190_REG_POC_CHG_CONFIG_MASK,
BQ24190_REG_POC_CHG_CONFIG_SHIFT,
v);
if (error < 0)
dev_err(bdi->dev, "Can't set CHG_CONFIG: %d\n", error);
ret = bq24190_read(bdi, BQ24190_REG_SS, &bdi->ss_reg);
out:
pm_runtime_put_sync(bdi->dev);
return ret;
pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
}
#ifdef CONFIG_OF
static int bq24190_setup_dt(struct bq24190_dev_info *bdi)
static int bq24190_extcon_event(struct notifier_block *nb, unsigned long event,
void *param)
{
bdi->irq = irq_of_parse_and_map(bdi->dev->of_node, 0);
if (bdi->irq <= 0)
return -1;
struct bq24190_dev_info *bdi =
container_of(nb, struct bq24190_dev_info, extcon_nb);
return 0;
}
#else
static int bq24190_setup_dt(struct bq24190_dev_info *bdi)
{
return -1;
/*
* The Power-Good detection may take up to 220ms, sometimes
* the external charger detection is quicker, and the bq24190 will
* reset to iinlim based on its own charger detection (which is not
* hooked up when using external charger detection) resulting in
* a too low default 500mA iinlim. Delay applying the extcon value
* for 300ms to avoid this.
*/
queue_delayed_work(system_wq, &bdi->extcon_work, msecs_to_jiffies(300));
return NOTIFY_OK;
}
#endif
static int bq24190_setup_pdata(struct bq24190_dev_info *bdi,
struct bq24190_platform_data *pdata)
static int bq24190_hw_init(struct bq24190_dev_info *bdi)
{
u8 v;
int ret;
if (!gpio_is_valid(pdata->gpio_int))
return -1;
ret = gpio_request(pdata->gpio_int, dev_name(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)
return -1;
return ret;
ret = gpio_direction_input(pdata->gpio_int);
if (ret < 0)
goto out;
if (v != BQ24190_REG_VPRS_PN_24190 &&
v != BQ24190_REG_VPRS_PN_24192I) {
dev_err(bdi->dev, "Error unknown model: 0x%02x\n", v);
return -ENODEV;
}
bdi->irq = gpio_to_irq(pdata->gpio_int);
if (!bdi->irq)
goto out;
ret = bq24190_register_reset(bdi);
if (ret < 0)
return ret;
bdi->gpio_int = pdata->gpio_int;
return 0;
ret = bq24190_set_mode_host(bdi);
if (ret < 0)
return ret;
out:
gpio_free(pdata->gpio_int);
return -1;
return bq24190_read(bdi, BQ24190_REG_SS, &bdi->ss_reg);
}
static int bq24190_probe(struct i2c_client *client,
......@@ -1320,9 +1398,9 @@ static int bq24190_probe(struct i2c_client *client,
{
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 power_supply_config charger_cfg = {}, battery_cfg = {};
struct bq24190_dev_info *bdi;
const char *name;
int ret;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
......@@ -1338,7 +1416,6 @@ static int bq24190_probe(struct i2c_client *client,
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->f_reg = 0;
......@@ -1346,23 +1423,43 @@ static int bq24190_probe(struct i2c_client *client,
i2c_set_clientdata(client, bdi);
if (dev->of_node)
ret = bq24190_setup_dt(bdi);
else
ret = bq24190_setup_pdata(bdi, pdata);
if (ret) {
if (!client->irq) {
dev_err(dev, "Can't get irq info\n");
return -EINVAL;
}
/*
* Devicetree platforms should get extcon via phandle (not yet supported).
* On ACPI platforms, extcon clients may invoke us with:
* struct property_entry pe[] =
* { PROPERTY_ENTRY_STRING("extcon-name", client_name), ... };
* struct i2c_board_info bi =
* { .type = "bq24190", .addr = 0x6b, .properties = pe, .irq = irq };
* struct i2c_adapter ad = { ... };
* i2c_add_adapter(&ad);
* i2c_new_device(&ad, &bi);
*/
if (device_property_read_string(dev, "extcon-name", &name) == 0) {
bdi->extcon = extcon_get_extcon_dev(name);
if (!bdi->extcon)
return -EPROBE_DEFER;
dev_info(bdi->dev, "using extcon device %s\n", name);
}
pm_runtime_enable(dev);
pm_runtime_resume(dev);
pm_runtime_use_autosuspend(dev);
pm_runtime_set_autosuspend_delay(dev, 600);
ret = pm_runtime_get_sync(dev);
if (ret < 0) {
dev_err(dev, "pm_runtime_get failed: %i\n", ret);
goto out_pmrt;
}
ret = bq24190_hw_init(bdi);
if (ret < 0) {
dev_err(dev, "Hardware init failed\n");
goto out1;
goto out_pmrt;
}
charger_cfg.drv_data = bdi;
......@@ -1373,7 +1470,7 @@ static int bq24190_probe(struct i2c_client *client,
if (IS_ERR(bdi->charger)) {
dev_err(dev, "Can't register charger\n");
ret = PTR_ERR(bdi->charger);
goto out1;
goto out_pmrt;
}
battery_cfg.drv_data = bdi;
......@@ -1382,87 +1479,160 @@ static int bq24190_probe(struct i2c_client *client,
if (IS_ERR(bdi->battery)) {
dev_err(dev, "Can't register battery\n");
ret = PTR_ERR(bdi->battery);
goto out2;
goto out_charger;
}
ret = bq24190_sysfs_create_group(bdi);
if (ret) {
dev_err(dev, "Can't create sysfs entries\n");
goto out3;
goto out_battery;
}
ret = devm_request_threaded_irq(dev, bdi->irq, NULL,
bdi->initialized = true;
ret = devm_request_threaded_irq(dev, client->irq, NULL,
bq24190_irq_handler_thread,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
"bq24190-charger", bdi);
if (ret < 0) {
dev_err(dev, "Can't set up irq handler\n");
goto out4;
goto out_sysfs;
}
if (bdi->extcon) {
INIT_DELAYED_WORK(&bdi->extcon_work, bq24190_extcon_work);
bdi->extcon_nb.notifier_call = bq24190_extcon_event;
ret = devm_extcon_register_notifier_all(dev, bdi->extcon,
&bdi->extcon_nb);
if (ret) {
dev_err(dev, "Can't register extcon\n");
goto out_sysfs;
}
/* Sync initial cable state */
queue_delayed_work(system_wq, &bdi->extcon_work, 0);
}
enable_irq_wake(client->irq);
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return 0;
out4:
out_sysfs:
bq24190_sysfs_remove_group(bdi);
out3:
out_battery:
power_supply_unregister(bdi->battery);
out2:
out_charger:
power_supply_unregister(bdi->charger);
out1:
out_pmrt:
pm_runtime_put_sync(dev);
pm_runtime_dont_use_autosuspend(dev);
pm_runtime_disable(dev);
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);
int error;
pm_runtime_get_sync(bdi->dev);
bq24190_register_reset(bdi);
pm_runtime_put_sync(bdi->dev);
error = pm_runtime_get_sync(bdi->dev);
if (error < 0) {
dev_warn(bdi->dev, "pm_runtime_get failed: %i\n", error);
pm_runtime_put_noidle(bdi->dev);
}
bq24190_register_reset(bdi);
bq24190_sysfs_remove_group(bdi);
power_supply_unregister(bdi->battery);
power_supply_unregister(bdi->charger);
if (error >= 0)
pm_runtime_put_sync(bdi->dev);
pm_runtime_dont_use_autosuspend(bdi->dev);
pm_runtime_disable(bdi->dev);
if (bdi->gpio_int)
gpio_free(bdi->gpio_int);
return 0;
}
static __maybe_unused int bq24190_runtime_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct bq24190_dev_info *bdi = i2c_get_clientdata(client);
if (!bdi->initialized)
return 0;
dev_dbg(bdi->dev, "%s\n", __func__);
return 0;
}
static __maybe_unused int bq24190_runtime_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct bq24190_dev_info *bdi = i2c_get_clientdata(client);
if (!bdi->initialized)
return 0;
if (!bdi->irq_event) {
dev_dbg(bdi->dev, "checking events on possible wakeirq\n");
bq24190_check_status(bdi);
}
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int bq24190_pm_suspend(struct device *dev)
static __maybe_unused int bq24190_pm_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct bq24190_dev_info *bdi = i2c_get_clientdata(client);
int error;
error = pm_runtime_get_sync(bdi->dev);
if (error < 0) {
dev_warn(bdi->dev, "pm_runtime_get failed: %i\n", error);
pm_runtime_put_noidle(bdi->dev);
}
pm_runtime_get_sync(bdi->dev);
bq24190_register_reset(bdi);
pm_runtime_put_sync(bdi->dev);
if (error >= 0) {
pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
}
return 0;
}
static int bq24190_pm_resume(struct device *dev)
static __maybe_unused int bq24190_pm_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct bq24190_dev_info *bdi = i2c_get_clientdata(client);
int error;
bdi->f_reg = 0;
bdi->ss_reg = BQ24190_REG_SS_VBUS_STAT_MASK; /* impossible state */
pm_runtime_get_sync(bdi->dev);
error = pm_runtime_get_sync(bdi->dev);
if (error < 0) {
dev_warn(bdi->dev, "pm_runtime_get failed: %i\n", error);
pm_runtime_put_noidle(bdi->dev);
}
bq24190_register_reset(bdi);
bq24190_set_mode_host(bdi);
bq24190_read(bdi, BQ24190_REG_SS, &bdi->ss_reg);
pm_runtime_put_sync(bdi->dev);
if (error >= 0) {
pm_runtime_mark_last_busy(bdi->dev);
pm_runtime_put_autosuspend(bdi->dev);
}
/* Things may have changed while suspended so alert upper layer */
power_supply_changed(bdi->charger);
......@@ -1470,17 +1640,16 @@ static int bq24190_pm_resume(struct device *dev)
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(bq24190_pm_ops, bq24190_pm_suspend, bq24190_pm_resume);
static const struct dev_pm_ops bq24190_pm_ops = {
SET_RUNTIME_PM_OPS(bq24190_runtime_suspend, bq24190_runtime_resume,
NULL)
SET_SYSTEM_SLEEP_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 },
{ "bq24190" },
{ "bq24192i" },
{ },
};
MODULE_DEVICE_TABLE(i2c, bq24190_i2c_ids);
......
......@@ -723,7 +723,7 @@ static int bq25890_irq_probe(struct bq25890_device *bq)
{
struct gpio_desc *irq;
irq = devm_gpiod_get_index(bq->dev, BQ25890_IRQ_PIN, 0, GPIOD_IN);
irq = devm_gpiod_get(bq->dev, BQ25890_IRQ_PIN, GPIOD_IN);
if (IS_ERR(irq)) {
dev_err(bq->dev, "Could not probe irq pin.\n");
return PTR_ERR(irq);
......
......@@ -1198,7 +1198,7 @@ static int charger_extcon_notifier(struct notifier_block *self,
static int charger_extcon_init(struct charger_manager *cm,
struct charger_cable *cable)
{
int ret = 0;
int ret;
/*
* Charger manager use Extcon framework to identify
......@@ -1232,7 +1232,7 @@ static int charger_manager_register_extcon(struct charger_manager *cm)
{
struct charger_desc *desc = cm->desc;
struct charger_regulator *charger;
int ret = 0;
int ret;
int i;
int j;
......@@ -1255,15 +1255,14 @@ static int charger_manager_register_extcon(struct charger_manager *cm)
if (ret < 0) {
dev_err(cm->dev, "Cannot initialize charger(%s)\n",
charger->regulator_name);
goto err;
return ret;
}
cable->charger = charger;
cable->cm = cm;
}
}
err:
return ret;
return 0;
}
/* help function of sysfs node to control charger(regulator) */
......@@ -1372,7 +1371,7 @@ static int charger_manager_register_sysfs(struct charger_manager *cm)
int chargers_externally_control = 1;
char buf[11];
char *str;
int ret = 0;
int ret;
int i;
/* Create sysfs entry to control charger(regulator) */
......@@ -1382,10 +1381,9 @@ static int charger_manager_register_sysfs(struct charger_manager *cm)
snprintf(buf, 10, "charger.%d", i);
str = devm_kzalloc(cm->dev,
sizeof(char) * (strlen(buf) + 1), GFP_KERNEL);
if (!str) {
ret = -ENOMEM;
goto err;
}
if (!str)
return -ENOMEM;
strcpy(str, buf);
charger->attrs[0] = &charger->attr_name.attr;
......@@ -1426,19 +1424,16 @@ static int charger_manager_register_sysfs(struct charger_manager *cm)
if (ret < 0) {
dev_err(cm->dev, "Cannot create sysfs entry of %s regulator\n",
charger->regulator_name);
ret = -EINVAL;
goto err;
return ret;
}
}
if (chargers_externally_control) {
dev_err(cm->dev, "Cannot register regulator because charger-manager must need at least one charger for charging battery\n");
ret = -EINVAL;
goto err;
return -EINVAL;
}
err:
return ret;
return 0;
}
static int cm_init_thermal_data(struct charger_manager *cm,
......@@ -1626,7 +1621,7 @@ static int charger_manager_probe(struct platform_device *pdev)
{
struct charger_desc *desc = cm_get_drv_data(pdev);
struct charger_manager *cm;
int ret = 0, i = 0;
int ret, i = 0;
int j = 0;
union power_supply_propval val;
struct power_supply *fuel_gauge;
......@@ -1887,14 +1882,12 @@ MODULE_DEVICE_TABLE(platform, charger_manager_id);
static int cm_suspend_noirq(struct device *dev)
{
int ret = 0;
if (device_may_wakeup(dev)) {
device_set_wakeup_capable(dev, false);
ret = -EAGAIN;
return -EAGAIN;
}
return ret;
return 0;
}
static bool cm_need_to_awake(void)
......
/*
* Motorola CPCAP PMIC battery charger driver
*
* Copyright (C) 2017 Tony Lindgren <tony@atomide.com>
*
* Rewritten for Linux power framework with some parts based on
* on earlier driver found in the Motorola Linux kernel:
*
* 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 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/atomic.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/notifier.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/gpio/consumer.h>
#include <linux/usb/phy_companion.h>
#include <linux/phy/omap_usb.h>
#include <linux/usb/otg.h>
#include <linux/iio/consumer.h>
#include <linux/mfd/motorola-cpcap.h>
/* CPCAP_REG_CRM register bits */
#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_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_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_ICHRG2 BIT(2)
#define CPCAP_REG_CRM_ICHRG1 BIT(1)
#define CPCAP_REG_CRM_ICHRG0 BIT(0)
/* CPCAP_REG_CRM trickle charge voltages */
#define CPCAP_REG_CRM_TR(val) (((val) & 0x3) << 10)
#define CPCAP_REG_CRM_TR_0A00 CPCAP_REG_CRM_TR(0x0)
#define CPCAP_REG_CRM_TR_0A24 CPCAP_REG_CRM_TR(0x1)
#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 */
#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_4V36 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 */
#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_NO_LIMIT CPCAP_REG_CRM_ICHRG(0xf)
enum {
CPCAP_CHARGER_IIO_BATTDET,
CPCAP_CHARGER_IIO_VOLTAGE,
CPCAP_CHARGER_IIO_VBUS,
CPCAP_CHARGER_IIO_CHRG_CURRENT,
CPCAP_CHARGER_IIO_BATT_CURRENT,
CPCAP_CHARGER_IIO_NR,
};
struct cpcap_charger_ddata {
struct device *dev;
struct regmap *reg;
struct list_head irq_list;
struct delayed_work detect_work;
struct delayed_work vbus_work;
struct gpio_desc *gpio[2]; /* gpio_reven0 & 1 */
struct iio_channel *channels[CPCAP_CHARGER_IIO_NR];
struct power_supply *usb;
struct phy_companion comparator; /* For USB VBUS */
bool vbus_enabled;
atomic_t active;
int status;
};
struct cpcap_interrupt_desc {
int irq;
struct list_head node;
const char *name;
};
struct cpcap_charger_ints_state {
bool chrg_det;
bool rvrs_chrg;
bool vbusov;
bool chrg_se1b;
bool rvrs_mode;
bool chrgcurr1;
bool vbusvld;
bool battdetb;
};
static enum power_supply_property cpcap_charger_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
};
static bool cpcap_charger_battery_found(struct cpcap_charger_ddata *ddata)
{
struct iio_channel *channel;
int error, value;
channel = ddata->channels[CPCAP_CHARGER_IIO_BATTDET];
error = iio_read_channel_raw(channel, &value);
if (error < 0) {
dev_warn(ddata->dev, "%s failed: %i\n", __func__, error);
return false;
}
return value == 1;
}
static int cpcap_charger_get_charge_voltage(struct cpcap_charger_ddata *ddata)
{
struct iio_channel *channel;
int error, value = 0;
channel = ddata->channels[CPCAP_CHARGER_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;
}
static int cpcap_charger_get_charge_current(struct cpcap_charger_ddata *ddata)
{
struct iio_channel *channel;
int error, value = 0;
channel = ddata->channels[CPCAP_CHARGER_IIO_CHRG_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;
}
static int cpcap_charger_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct cpcap_charger_ddata *ddata = dev_get_drvdata(psy->dev.parent);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = ddata->status;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
if (ddata->status == POWER_SUPPLY_STATUS_CHARGING)
val->intval = cpcap_charger_get_charge_voltage(ddata) *
1000;
else
val->intval = 0;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
if (ddata->status == POWER_SUPPLY_STATUS_CHARGING)
val->intval = cpcap_charger_get_charge_current(ddata) *
1000;
else
val->intval = 0;
break;
case POWER_SUPPLY_PROP_ONLINE:
val->intval = ddata->status == POWER_SUPPLY_STATUS_CHARGING;
break;
default:
return -EINVAL;
}
return 0;
}
static void cpcap_charger_set_cable_path(struct cpcap_charger_ddata *ddata,
bool enabled)
{
if (!ddata->gpio[0])
return;
gpiod_set_value(ddata->gpio[0], enabled);
}
static void cpcap_charger_set_inductive_path(struct cpcap_charger_ddata *ddata,
bool enabled)
{
if (!ddata->gpio[1])
return;
gpiod_set_value(ddata->gpio[1], enabled);
}
static int cpcap_charger_set_state(struct cpcap_charger_ddata *ddata,
int max_voltage, int charge_current,
int trickle_current)
{
bool enable;
int error;
enable = max_voltage && (charge_current || trickle_current);
dev_dbg(ddata->dev, "%s enable: %i\n", __func__, enable);
if (!enable) {
error = regmap_update_bits(ddata->reg, CPCAP_REG_CRM,
0x3fff,
CPCAP_REG_CRM_FET_OVRD |
CPCAP_REG_CRM_FET_CTRL);
if (error) {
ddata->status = POWER_SUPPLY_STATUS_UNKNOWN;
goto out_err;
}
ddata->status = POWER_SUPPLY_STATUS_DISCHARGING;
return 0;
}
error = regmap_update_bits(ddata->reg, CPCAP_REG_CRM, 0x3fff,
CPCAP_REG_CRM_CHRG_LED_EN |
trickle_current |
CPCAP_REG_CRM_FET_OVRD |
CPCAP_REG_CRM_FET_CTRL |
max_voltage |
charge_current);
if (error) {
ddata->status = POWER_SUPPLY_STATUS_UNKNOWN;
goto out_err;
}
ddata->status = POWER_SUPPLY_STATUS_CHARGING;
return 0;
out_err:
dev_err(ddata->dev, "%s failed with %i\n", __func__, error);
return error;
}
static bool cpcap_charger_vbus_valid(struct cpcap_charger_ddata *ddata)
{
int error, value = 0;
struct iio_channel *channel =
ddata->channels[CPCAP_CHARGER_IIO_VBUS];
error = iio_read_channel_processed(channel, &value);
if (error >= 0)
return value > 3900 ? true : false;
dev_err(ddata->dev, "error reading VBUS: %i\n", error);
return false;
}
/* VBUS control functions for the USB PHY companion */
static void cpcap_charger_vbus_work(struct work_struct *work)
{
struct cpcap_charger_ddata *ddata;
bool vbus = false;
int error;
ddata = container_of(work, struct cpcap_charger_ddata,
vbus_work.work);
if (ddata->vbus_enabled) {
vbus = cpcap_charger_vbus_valid(ddata);
if (vbus) {
dev_info(ddata->dev, "VBUS already provided\n");
return;
}
cpcap_charger_set_cable_path(ddata, false);
cpcap_charger_set_inductive_path(ddata, false);
error = cpcap_charger_set_state(ddata, 0, 0, 0);
if (error)
goto out_err;
error = regmap_update_bits(ddata->reg, CPCAP_REG_CRM,
CPCAP_REG_CRM_RVRSMODE,
CPCAP_REG_CRM_RVRSMODE);
if (error)
goto out_err;
} else {
error = regmap_update_bits(ddata->reg, CPCAP_REG_CRM,
CPCAP_REG_CRM_RVRSMODE, 0);
if (error)
goto out_err;
cpcap_charger_set_cable_path(ddata, true);
cpcap_charger_set_inductive_path(ddata, true);
}
return;
out_err:
dev_err(ddata->dev, "%s could not %s vbus: %i\n", __func__,
ddata->vbus_enabled ? "enable" : "disable", error);
}
static int cpcap_charger_set_vbus(struct phy_companion *comparator,
bool enabled)
{
struct cpcap_charger_ddata *ddata =
container_of(comparator, struct cpcap_charger_ddata,
comparator);
ddata->vbus_enabled = enabled;
schedule_delayed_work(&ddata->vbus_work, 0);
return 0;
}
/* Charger interrupt handling functions */
static int cpcap_charger_get_ints_state(struct cpcap_charger_ddata *ddata,
struct cpcap_charger_ints_state *s)
{
int val, error;
error = regmap_read(ddata->reg, CPCAP_REG_INTS1, &val);
if (error)
return error;
s->chrg_det = val & BIT(13);
s->rvrs_chrg = val & BIT(12);
s->vbusov = val & BIT(11);
error = regmap_read(ddata->reg, CPCAP_REG_INTS2, &val);
if (error)
return error;
s->chrg_se1b = val & BIT(13);
s->rvrs_mode = val & BIT(6);
s->chrgcurr1 = val & BIT(4);
s->vbusvld = val & BIT(3);
error = regmap_read(ddata->reg, CPCAP_REG_INTS4, &val);
if (error)
return error;
s->battdetb = val & BIT(6);
return 0;
}
static void cpcap_usb_detect(struct work_struct *work)
{
struct cpcap_charger_ddata *ddata;
struct cpcap_charger_ints_state s;
int error;
ddata = container_of(work, struct cpcap_charger_ddata,
detect_work.work);
error = cpcap_charger_get_ints_state(ddata, &s);
if (error)
return;
if (cpcap_charger_vbus_valid(ddata) && s.chrgcurr1) {
int max_current;
if (cpcap_charger_battery_found(ddata))
max_current = CPCAP_REG_CRM_ICHRG_1A584;
else
max_current = CPCAP_REG_CRM_ICHRG_0A528;
error = cpcap_charger_set_state(ddata,
CPCAP_REG_CRM_VCHRG_4V20,
max_current,
CPCAP_REG_CRM_TR_0A72);
if (error)
goto out_err;
} else {
error = cpcap_charger_set_state(ddata, 0, 0, 0);
if (error)
goto out_err;
}
return;
out_err:
dev_err(ddata->dev, "%s failed with %i\n", __func__, error);
}
static irqreturn_t cpcap_charger_irq_thread(int irq, void *data)
{
struct cpcap_charger_ddata *ddata = data;
if (!atomic_read(&ddata->active))
return IRQ_NONE;
schedule_delayed_work(&ddata->detect_work, 0);
return IRQ_HANDLED;
}
static int cpcap_usb_init_irq(struct platform_device *pdev,
struct cpcap_charger_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_charger_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;
list_add(&d->node, &ddata->irq_list);
return 0;
}
static const char * const cpcap_charger_irqs[] = {
/* REG_INT_0 */
"chrg_det", "rvrs_chrg",
/* REG_INT1 */
"chrg_se1b", "se0conn", "rvrs_mode", "chrgcurr1", "vbusvld",
/* REG_INT_3 */
"battdetb",
};
static int cpcap_usb_init_interrupts(struct platform_device *pdev,
struct cpcap_charger_ddata *ddata)
{
int i, error;
for (i = 0; i < ARRAY_SIZE(cpcap_charger_irqs); i++) {
error = cpcap_usb_init_irq(pdev, ddata, cpcap_charger_irqs[i]);
if (error)
return error;
}
return 0;
}
static void cpcap_charger_init_optional_gpios(struct cpcap_charger_ddata *ddata)
{
int i;
for (i = 0; i < 2; i++) {
ddata->gpio[i] = devm_gpiod_get_index(ddata->dev, "mode",
i, GPIOD_OUT_HIGH);
if (IS_ERR(ddata->gpio[i])) {
dev_info(ddata->dev, "no mode change GPIO%i: %li\n",
i, PTR_ERR(ddata->gpio[i]));
ddata->gpio[i] = NULL;
}
}
}
static int cpcap_charger_init_iio(struct cpcap_charger_ddata *ddata)
{
const char * const names[CPCAP_CHARGER_IIO_NR] = {
"battdetb", "battp", "vbus", "chg_isense", "batti",
};
int error, i;
for (i = 0; i < CPCAP_CHARGER_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;
}
static const struct power_supply_desc cpcap_charger_usb_desc = {
.name = "cpcap_usb",
.type = POWER_SUPPLY_TYPE_USB,
.properties = cpcap_charger_props,
.num_properties = ARRAY_SIZE(cpcap_charger_props),
.get_property = cpcap_charger_get_property,
};
#ifdef CONFIG_OF
static const struct of_device_id cpcap_charger_id_table[] = {
{
.compatible = "motorola,mapphone-cpcap-charger",
},
{},
};
MODULE_DEVICE_TABLE(of, cpcap_charger_id_table);
#endif
static int cpcap_charger_probe(struct platform_device *pdev)
{
struct cpcap_charger_ddata *ddata;
const struct of_device_id *of_id;
int error;
of_id = of_match_device(of_match_ptr(cpcap_charger_id_table),
&pdev->dev);
if (!of_id)
return -EINVAL;
ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata)
return -ENOMEM;
ddata->dev = &pdev->dev;
ddata->reg = dev_get_regmap(ddata->dev->parent, NULL);
if (!ddata->reg)
return -ENODEV;
INIT_LIST_HEAD(&ddata->irq_list);
INIT_DELAYED_WORK(&ddata->detect_work, cpcap_usb_detect);
INIT_DELAYED_WORK(&ddata->vbus_work, cpcap_charger_vbus_work);
platform_set_drvdata(pdev, ddata);
error = cpcap_charger_init_iio(ddata);
if (error)
return error;
atomic_set(&ddata->active, 1);
ddata->usb = devm_power_supply_register(ddata->dev,
&cpcap_charger_usb_desc,
NULL);
if (IS_ERR(ddata->usb)) {
error = PTR_ERR(ddata->usb);
dev_err(ddata->dev, "failed to register USB charger: %i\n",
error);
return error;
}
error = cpcap_usb_init_interrupts(pdev, ddata);
if (error)
return error;
ddata->comparator.set_vbus = cpcap_charger_set_vbus;
error = omap_usb2_set_comparator(&ddata->comparator);
if (error == -ENODEV) {
dev_info(ddata->dev, "charger needs phy, deferring probe\n");
return -EPROBE_DEFER;
}
cpcap_charger_init_optional_gpios(ddata);
schedule_delayed_work(&ddata->detect_work, 0);
return 0;
}
static int cpcap_charger_remove(struct platform_device *pdev)
{
struct cpcap_charger_ddata *ddata = platform_get_drvdata(pdev);
int error;
atomic_set(&ddata->active, 0);
error = omap_usb2_set_comparator(NULL);
if (error)
dev_warn(ddata->dev, "could not clear USB comparator: %i\n",
error);
error = cpcap_charger_set_state(ddata, 0, 0, 0);
if (error)
dev_warn(ddata->dev, "could not clear charger: %i\n",
error);
cancel_delayed_work_sync(&ddata->vbus_work);
cancel_delayed_work_sync(&ddata->detect_work);
return 0;
}
static struct platform_driver cpcap_charger_driver = {
.probe = cpcap_charger_probe,
.driver = {
.name = "cpcap-charger",
.of_match_table = of_match_ptr(cpcap_charger_id_table),
},
.remove = cpcap_charger_remove,
};
module_platform_driver(cpcap_charger_driver);
MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
MODULE_DESCRIPTION("CPCAP Battery Charger Interface driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:cpcap-charger");
/*
* Battery driver for LEGO MINDSTORMS EV3
*
* Copyright (C) 2017 David Lechner <david@lechnology.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
* This program is distributed "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/gpio/consumer.h>
#include <linux/iio/consumer.h>
#include <linux/iio/types.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
struct lego_ev3_battery {
struct iio_channel *iio_v;
struct iio_channel *iio_i;
struct gpio_desc *rechargeable_gpio;
struct power_supply *psy;
int technology;
int v_max;
int v_min;
};
static int lego_ev3_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct lego_ev3_battery *batt = power_supply_get_drvdata(psy);
int val2;
switch (psp) {
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = batt->technology;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
/* battery voltage is iio channel * 2 + Vce of transistor */
iio_read_channel_processed(batt->iio_v, &val->intval);
val->intval *= 2000;
val->intval += 200000;
/* plus adjust for shunt resistor drop */
iio_read_channel_processed(batt->iio_i, &val2);
val2 *= 1000;
val2 /= 15;
val->intval += val2;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
val->intval = batt->v_max;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
val->intval = batt->v_min;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
/* battery current is iio channel / 15 / 0.05 ohms */
iio_read_channel_processed(batt->iio_i, &val->intval);
val->intval *= 20000;
val->intval /= 15;
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
break;
default:
return -EINVAL;
}
return 0;
}
static int lego_ev3_battery_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct lego_ev3_battery *batt = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_TECHNOLOGY:
/*
* Only allow changing technology from Unknown to NiMH. Li-ion
* batteries are automatically detected and should not be
* overridden. Rechargeable AA batteries, on the other hand,
* cannot be automatically detected, and so must be manually
* specified. This should only be set once during system init,
* so there is no mechanism to go back to Unknown.
*/
if (batt->technology != POWER_SUPPLY_TECHNOLOGY_UNKNOWN)
return -EINVAL;
switch (val->intval) {
case POWER_SUPPLY_TECHNOLOGY_NiMH:
batt->technology = POWER_SUPPLY_TECHNOLOGY_NiMH;
batt->v_max = 7800000;
batt->v_min = 5400000;
break;
default:
return -EINVAL;
}
break;
default:
return -EINVAL;
}
return 0;
}
static int lego_ev3_battery_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
struct lego_ev3_battery *batt = power_supply_get_drvdata(psy);
return psp == POWER_SUPPLY_PROP_TECHNOLOGY &&
batt->technology == POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
}
static enum power_supply_property lego_ev3_battery_props[] = {
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_NOW,
POWER_SUPPLY_PROP_SCOPE,
};
static const struct power_supply_desc lego_ev3_battery_desc = {
.name = "lego-ev3-battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = lego_ev3_battery_props,
.num_properties = ARRAY_SIZE(lego_ev3_battery_props),
.get_property = lego_ev3_battery_get_property,
.set_property = lego_ev3_battery_set_property,
.property_is_writeable = lego_ev3_battery_property_is_writeable,
};
static int lego_ev3_battery_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct lego_ev3_battery *batt;
struct power_supply_config psy_cfg = {};
int err;
batt = devm_kzalloc(dev, sizeof(*batt), GFP_KERNEL);
if (!batt)
return -ENOMEM;
platform_set_drvdata(pdev, batt);
batt->iio_v = devm_iio_channel_get(dev, "voltage");
err = PTR_ERR_OR_ZERO(batt->iio_v);
if (err) {
if (err != -EPROBE_DEFER)
dev_err(dev, "Failed to get voltage iio channel\n");
return err;
}
batt->iio_i = devm_iio_channel_get(dev, "current");
err = PTR_ERR_OR_ZERO(batt->iio_i);
if (err) {
if (err != -EPROBE_DEFER)
dev_err(dev, "Failed to get current iio channel\n");
return err;
}
batt->rechargeable_gpio = devm_gpiod_get(dev, "rechargeable", GPIOD_IN);
err = PTR_ERR_OR_ZERO(batt->rechargeable_gpio);
if (err) {
if (err != -EPROBE_DEFER)
dev_err(dev, "Failed to get rechargeable gpio\n");
return err;
}
/*
* The rechargeable battery indication switch cannot be changed without
* removing the battery, so we only need to read it once.
*/
if (gpiod_get_value(batt->rechargeable_gpio)) {
/* 2-cell Li-ion, 7.4V nominal */
batt->technology = POWER_SUPPLY_TECHNOLOGY_LION;
batt->v_max = 84000000;
batt->v_min = 60000000;
} else {
/* 6x AA Alkaline, 9V nominal */
batt->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
batt->v_max = 90000000;
batt->v_min = 48000000;
}
psy_cfg.of_node = pdev->dev.of_node;
psy_cfg.drv_data = batt;
batt->psy = devm_power_supply_register(dev, &lego_ev3_battery_desc,
&psy_cfg);
err = PTR_ERR_OR_ZERO(batt->psy);
if (err) {
dev_err(dev, "failed to register power supply\n");
return err;
}
return 0;
}
static const struct of_device_id of_lego_ev3_battery_match[] = {
{ .compatible = "lego,ev3-battery", },
{ }
};
MODULE_DEVICE_TABLE(of, of_lego_ev3_battery_match);
static struct platform_driver lego_ev3_battery_driver = {
.driver = {
.name = "lego-ev3-battery",
.of_match_table = of_lego_ev3_battery_match,
},
.probe = lego_ev3_battery_probe,
};
module_platform_driver(lego_ev3_battery_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("David Lechner <david@lechnology.com>");
MODULE_DESCRIPTION("LEGO MINDSTORMS EV3 Battery Driver");
......@@ -651,7 +651,7 @@ static ssize_t lp8788_show_eoc_time(struct device *dev,
{
struct lp8788_charger *pchg = dev_get_drvdata(dev);
char *stime[] = { "400ms", "5min", "10min", "15min",
"20min", "25min", "30min" "No timeout" };
"20min", "25min", "30min", "No timeout" };
u8 val;
lp8788_read_byte(pchg->lp, LP8788_CHG_EOC, &val);
......
......@@ -9,6 +9,7 @@
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/swab.h>
......@@ -61,7 +62,7 @@ struct ltc294x_info {
struct power_supply *supply; /* Supply pointer */
struct power_supply_desc supply_desc; /* Supply description */
struct delayed_work work; /* Work scheduler */
int num_regs; /* Number of registers (chip type) */
unsigned long num_regs; /* Number of registers (chip type) */
int charge; /* Last charge register content */
int r_sense; /* mOhm */
int Qlsb; /* nAh */
......@@ -387,7 +388,7 @@ static int ltc294x_i2c_probe(struct i2c_client *client,
np = of_node_get(client->dev.of_node);
info->num_regs = id->driver_data;
info->num_regs = (unsigned long)of_device_get_match_data(&client->dev);
info->supply_desc.name = np->name;
/* r_sense can be negative, when sense+ is connected to the battery
......@@ -497,9 +498,23 @@ static const struct i2c_device_id ltc294x_i2c_id[] = {
};
MODULE_DEVICE_TABLE(i2c, ltc294x_i2c_id);
static const struct of_device_id ltc294x_i2c_of_match[] = {
{
.compatible = "lltc,ltc2941",
.data = (void *)LTC2941_NUM_REGS
},
{
.compatible = "lltc,ltc2943",
.data = (void *)LTC2943_NUM_REGS
},
{ },
};
MODULE_DEVICE_TABLE(of, ltc294x_i2c_of_match);
static struct i2c_driver ltc294x_driver = {
.driver = {
.name = "LTC2941",
.of_match_table = ltc294x_i2c_of_match,
.pm = LTC294X_PM_OPS,
},
.probe = ltc294x_i2c_probe,
......
......@@ -277,9 +277,17 @@ static const struct i2c_device_id max17040_id[] = {
};
MODULE_DEVICE_TABLE(i2c, max17040_id);
static const struct of_device_id max17040_of_match[] = {
{ .compatible = "maxim,max17040" },
{ .compatible = "maxim,max77836-battery" },
{ },
};
MODULE_DEVICE_TABLE(of, max17040_of_match);
static struct i2c_driver max17040_i2c_driver = {
.driver = {
.name = "max17040",
.of_match_table = max17040_of_match,
.pm = MAX17040_PM_OPS,
},
.probe = max17040_probe,
......
......@@ -137,10 +137,7 @@ static enum power_supply_property sbs_properties[] = {
static bool sbs_readable_reg(struct device *dev, unsigned int reg)
{
if (reg < SBS_CHARGER_REG_SPEC_INFO)
return false;
else
return true;
return reg >= SBS_CHARGER_REG_SPEC_INFO;
}
static bool sbs_volatile_reg(struct device *dev, unsigned int reg)
......
......@@ -58,8 +58,6 @@ static int tps65217_config_charger(struct tps65217_charger *charger)
{
int ret;
dev_dbg(charger->dev, "%s\n", __func__);
/*
* tps65217 rev. G, p. 31 (see p. 32 for NTC schematic)
*
......@@ -205,8 +203,6 @@ static int tps65217_charger_probe(struct platform_device *pdev)
int ret;
int i;
dev_dbg(&pdev->dev, "%s\n", __func__);
charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL);
if (!charger)
return -ENOMEM;
......
......@@ -1117,7 +1117,7 @@ static int twl4030_bci_probe(struct platform_device *pdev)
return ret;
}
static int __exit twl4030_bci_remove(struct platform_device *pdev)
static int twl4030_bci_remove(struct platform_device *pdev)
{
struct twl4030_bci *bci = platform_get_drvdata(pdev);
......@@ -1148,11 +1148,11 @@ MODULE_DEVICE_TABLE(of, twl_bci_of_match);
static struct platform_driver twl4030_bci_driver = {
.probe = twl4030_bci_probe,
.remove = twl4030_bci_remove,
.driver = {
.name = "twl4030_bci",
.of_match_table = of_match_ptr(twl_bci_of_match),
},
.remove = __exit_p(twl4030_bci_remove),
};
module_platform_driver(twl4030_bci_driver);
......
......@@ -236,11 +236,11 @@ extern int extcon_set_property_capability(struct extcon_dev *edev,
unsigned int id, unsigned int prop);
/*
* Following APIs are to monitor every action of a notifier.
* Registrar gets notified for every external port of a connection device.
* Probably this could be used to debug an action of notifier; however,
* we do not recommend to use this for normal 'notifiee' device drivers who
* want to be notified by a specific external port of the notifier.
* Following APIs are to monitor the status change of the external connectors.
* extcon_register_notifier(*edev, id, *nb) : Register a notifier block
* for specific external connector of the extcon.
* extcon_register_notifier_all(*edev, *nb) : Register a notifier block
* for all supported external connectors of the extcon.
*/
extern int extcon_register_notifier(struct extcon_dev *edev, unsigned int id,
struct notifier_block *nb);
......@@ -253,6 +253,17 @@ extern void devm_extcon_unregister_notifier(struct device *dev,
struct extcon_dev *edev, unsigned int id,
struct notifier_block *nb);
extern int extcon_register_notifier_all(struct extcon_dev *edev,
struct notifier_block *nb);
extern int extcon_unregister_notifier_all(struct extcon_dev *edev,
struct notifier_block *nb);
extern int devm_extcon_register_notifier_all(struct device *dev,
struct extcon_dev *edev,
struct notifier_block *nb);
extern void devm_extcon_unregister_notifier_all(struct device *dev,
struct extcon_dev *edev,
struct notifier_block *nb);
/*
* Following API get the extcon device from devicetree.
* This function use phandle of devicetree to get extcon device directly.
......
/*
* 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment