mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-19 18:53:52 +08:00
power supply and reset changes for the v4.12 series
* 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 * Misc. small fixes & cleanups -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEE72YNB0Y/i3JqeVQT2O7X88g7+poFAlkG8cIACgkQ2O7X88g7 +po/2RAAhIx/0rvZsrKO3qZdJQxln0CwmJOC/Pv0V582MPLMFbazDA8G6ShQ9zaF cMe8k6Z5JlHWPn7n9Sr8hUGmWkzgzPMNxU7u1fGsPUL3b8e6wiAC7RWhnJAZniqy RfKdAuP9kzLZkksbK8f9fTIgX36ttoNfSmfKBi+jl/pS7sO8sAlxhXrzXJ/lP8Uj 32BnjXFRPzQuwrFguauIVJLYv3PiNG5UkSuRBIzk63OaZ0iK0BoT+gz77N1Y24av TjeWkNrUodTRchh10FkwDxEtmPwTOtYgVNWMpb1CF1jSD9diq5gd8vQowVQBzW9x S5HbuoLq5+LDOGXZf8FUUp7w02fNlpNDxlOFZlrfa0/B+XOCnyVMD2tTOmIOB6j4 fDvrL3Z1K1jXi93QTEnEWWGKpPZo5BNOOHYkdSD1z9tHDBhyliRQ49VJ/huqhTy0 30XbYkiEoVxpoNOUuqj6Pmb47LP1UIAMCbmGHatSBC7FTrxbeB2NcliuLDngVkNv 3xFfeYwCCta1uGdx1AKIKYz4t6rC2PqtQN0iBzUU04MYPibvMejLcZjodqRrecGu HzBsUQahXWd4/itQj4d+8WtNLYSjP5S9kbDuZ2LJg+UOrbdO6BrwUPCfB0HTsAP6 L1C7hBQmFHSn7YtjfBERg3LKV78Be5FUqiYO+jrv/N5iNI7iKrY= =Empl -----END PGP SIGNATURE----- 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() ...
This commit is contained in:
commit
7f2ebde737
@ -0,0 +1,17 @@
|
||||
* 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.
|
||||
|
||||
|
@ -0,0 +1,37 @@
|
||||
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";
|
||||
};
|
@ -0,0 +1,21 @@
|
||||
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
|
||||
|
160
drivers/power/reset/gemini-poweroff.c
Normal file
160
drivers/power/reset/gemini-poweroff.c
Normal file
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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
|
||||
|
@ -430,10 +430,10 @@ static const struct abx500_maxim_parameters ab8500_maxi_params = {
|
||||
};
|
||||
|
||||
static const struct abx500_maxim_parameters abx540_maxi_params = {
|
||||
.ena_maxi = true,
|
||||
.chg_curr = 3000,
|
||||
.wait_cycles = 10,
|
||||
.charger_curr_step = 200,
|
||||
.ena_maxi = true,
|
||||
.chg_curr = 3000,
|
||||
.wait_cycles = 10,
|
||||
.charger_curr_step = 200,
|
||||
};
|
||||
|
||||
static const struct abx500_bm_charger_parameters chg = {
|
||||
|
@ -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 = bq24190_read_mask(bdi, info->reg, info->mask, info->shift, &v);
|
||||
if (ret)
|
||||
ret = pm_runtime_get_sync(bdi->dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%hhx\n", v);
|
||||
ret = bq24190_read_mask(bdi, info->reg, info->mask, info->shift, &v);
|
||||
if (ret)
|
||||
count = ret;
|
||||
else
|
||||
count = 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;
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* 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 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;
|
||||
|
||||
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 (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);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
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);
|
||||
|
||||
pm_runtime_mark_last_busy(bdi->dev);
|
||||
pm_runtime_put_autosuspend(bdi->dev);
|
||||
}
|
||||
|
||||
static int bq24190_extcon_event(struct notifier_block *nb, unsigned long event,
|
||||
void *param)
|
||||
{
|
||||
struct bq24190_dev_info *bdi =
|
||||
container_of(nb, struct bq24190_dev_info, extcon_nb);
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
static int bq24190_hw_init(struct bq24190_dev_info *bdi)
|
||||
{
|
||||
u8 v;
|
||||
int ret;
|
||||
|
||||
pm_runtime_get_sync(bdi->dev);
|
||||
|
||||
/* First check that the device really is what its supposed to be */
|
||||
ret = bq24190_read_mask(bdi, BQ24190_REG_VPRS,
|
||||
BQ24190_REG_VPRS_PN_MASK,
|
||||
BQ24190_REG_VPRS_PN_SHIFT,
|
||||
&v);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
return ret;
|
||||
|
||||
if (v != bdi->model) {
|
||||
ret = -ENODEV;
|
||||
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;
|
||||
}
|
||||
|
||||
ret = bq24190_register_reset(bdi);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
return ret;
|
||||
|
||||
ret = bq24190_set_mode_host(bdi);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
return ret;
|
||||
|
||||
ret = bq24190_read(bdi, BQ24190_REG_SS, &bdi->ss_reg);
|
||||
out:
|
||||
pm_runtime_put_sync(bdi->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static int bq24190_setup_dt(struct bq24190_dev_info *bdi)
|
||||
{
|
||||
bdi->irq = irq_of_parse_and_map(bdi->dev->of_node, 0);
|
||||
if (bdi->irq <= 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static int bq24190_setup_dt(struct bq24190_dev_info *bdi)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int bq24190_setup_pdata(struct bq24190_dev_info *bdi,
|
||||
struct bq24190_platform_data *pdata)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!gpio_is_valid(pdata->gpio_int))
|
||||
return -1;
|
||||
|
||||
ret = gpio_request(pdata->gpio_int, dev_name(bdi->dev));
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
ret = gpio_direction_input(pdata->gpio_int);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
bdi->irq = gpio_to_irq(pdata->gpio_int);
|
||||
if (!bdi->irq)
|
||||
goto out;
|
||||
|
||||
bdi->gpio_int = pdata->gpio_int;
|
||||
return 0;
|
||||
|
||||
out:
|
||||
gpio_free(pdata->gpio_int);
|
||||
return -1;
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int bq24190_pm_suspend(struct device *dev)
|
||||
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);
|
||||
|
||||
pm_runtime_get_sync(bdi->dev);
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
|
681
drivers/power/supply/cpcap-charger.c
Normal file
681
drivers/power/supply/cpcap-charger.c
Normal file
@ -0,0 +1,681 @@
|
||||
/*
|
||||
* 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");
|
228
drivers/power/supply/lego_ev3_battery.c
Normal file
228
drivers/power/supply/lego_ev3_battery.c
Normal file
@ -0,0 +1,228 @@
|
||||
/*
|
||||
* 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 @@ fail:
|
||||
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.
|
||||
|
@ -1,16 +0,0 @@
|
||||
/*
|
||||
* 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
|
Loading…
Reference in New Issue
Block a user