Commit c9b9f207 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'for-v4.11' 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:
   - sbs-charger driver
   - max14656_charger_detector
   - axp20x_ac_power

  New chip/feature support"
   - axp20x_usb_power: add AXP223 support
   - tps65217: add usb charger support
   - qcom_smbb: support otg regulator
   - at91-reset: add samx7 support

  Dropped drivers:
   - intel_mid_battery (platform was dropped)

  Fixes:
   - at91-poweroff: avoid wearing off LPDDR memory
   - replace deprecated extcon API
   - lots of cleanup and style fixes
   - misc minor functionality fixes"

* tag 'for-v4.11' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (84 commits)
  power: supply: add AC power supply driver for AXP20X and AXP22X PMICs
  dt-bindings: power: supply: add AXP20X/AXP22X AC power supply
  power: supply: axp20x_usb_power: use IIO channels when available
  power: supply: max14656: Export I2C and OF device ID as module aliases
  power: supply: bq2415x: check for NULL acpi_id to avoid null pointer dereference
  power: supply: bq24190_charger: Adjust formatting
  power: supply: bq24190_charger: Handle fault before status on interrupt
  power: supply: bq24190_charger: Don't read fault register outside irq_handle_thread()
  power: supply: bq24190_charger: Call power_supply_changed() for relevant component
  power: supply: bq24190_charger: Install irq_handler_thread() at end of probe()
  power: supply: bq24190_charger: Call set_mode_host() on pm_resume()
  power: supply: bq24190_charger: Fix irq trigger to IRQF_TRIGGER_FALLING
  power: supply: qcom_smbb: add regulator dependency
  power: reset: at91-reset: remove leftover platform_device_id
  power: reset: at91-reset: add samx7 support
  power: supply: max14656: fix platform_no_drv_owner.cocci warnings
  power: supply: pcf50633-charger: Compress return logic into one line.
  power: supply: ab8500_btemp: Compress return logic into one line.
  power: reset: at91-poweroff: timely shutdown LPDDR memories
  ARM: at91: define LPDDR types
  ...
parents 345fb0a9 744cc304
AXP20X and AXP22X PMICs' AC power supply
Required Properties:
- compatible: One of:
"x-powers,axp202-ac-power-supply"
"x-powers,axp221-ac-power-supply"
This node is a subnode of the axp20x PMIC.
The AXP20X can read the current current and voltage supplied by AC by
reading ADC channels from the AXP20X ADC.
The AXP22X is only able to tell if an AC power supply is present and
usable.
Example:
&axp209 {
ac_power_supply: ac-power-supply {
compatible = "x-powers,axp202-ac-power-supply";
};
};
......@@ -3,6 +3,11 @@ AXP20x USB power supply
Required Properties:
-compatible: One of: "x-powers,axp202-usb-power-supply"
"x-powers,axp221-usb-power-supply"
"x-powers,axp223-usb-power-supply"
The AXP223 PMIC shares most of its behaviour with the AXP221 but has slight
variations such as the former being able to set the VBUS power supply max
current to 100mA, unlike the latter.
This node is a subnode of the axp20x PMIC.
......
Binding for TI BQ27XXX fuel gauge family
Required properties:
- compatible: Should contain one of the following:
* "ti,bq27200" - BQ27200
* "ti,bq27210" - BQ27210
* "ti,bq27500" - deprecated, use revision specific property below
* "ti,bq27510" - deprecated, use revision specific property below
* "ti,bq27520" - deprecated, use revision specific property below
* "ti,bq27500-1" - BQ27500/1
* "ti,bq27510g1" - BQ27510-g1
* "ti,bq27510g2" - BQ27510-g2
* "ti,bq27510g3" - BQ27510-g3
* "ti,bq27520g1" - BQ27520-g1
* "ti,bq27520g2" - BQ27520-g2
* "ti,bq27520g3" - BQ27520-g3
* "ti,bq27520g4" - BQ27520-g4
* "ti,bq27530" - BQ27530
* "ti,bq27531" - BQ27531
* "ti,bq27541" - BQ27541
* "ti,bq27542" - BQ27542
* "ti,bq27546" - BQ27546
* "ti,bq27742" - BQ27742
* "ti,bq27545" - BQ27545
* "ti,bq27421" - BQ27421
* "ti,bq27425" - BQ27425
* "ti,bq27441" - BQ27441
* "ti,bq27621" - BQ27621
- reg: integer, i2c address of the device.
Example:
bq27510g3 {
compatible = "ti,bq27510g3";
reg = <0x55>;
};
......@@ -105,6 +105,22 @@ PROPERTIES
regulation must be done externally to fully comply with
the JEITA safety guidelines if this flag is set.
- usb_otg_in-supply:
Usage: optional
Value type: <phandle>
Description: Reference to the regulator supplying power to the USB_OTG_IN
pin.
child nodes:
- otg-vbus:
Usage: optional
Description: This node defines a regulator used to control the direction
of VBUS voltage - specifically: whether to supply voltage
to VBUS for host mode operation of the OTG port, or allow
input voltage from external VBUS for charging. In the
hardware, the supply for this regulator comes from
usb_otg_in-supply.
EXAMPLE
charger@1000 {
compatible = "qcom,pm8941-charger";
......@@ -128,4 +144,7 @@ charger@1000 {
qcom,fast-charge-current-limit = <1000000>;
qcom,dc-charge-current-limit = <1000000>;
usb_otg_in-supply = <&pm8941_5vs1>;
otg-vbus {};
};
SBS sbs-charger
~~~~~~~~~~
Required properties:
- compatible: "<vendor>,<part-number>", "sbs,sbs-charger" as fallback. The part
number compatible string might be used in order to take care of vendor
specific registers.
Optional properties:
- interrupt-parent: Should be the phandle for the interrupt controller. Use in
conjunction with "interrupts".
- interrupts: Interrupt mapping for GPIO IRQ. Use in conjunction with
"interrupt-parent". If an interrupt is not provided the driver will switch
automatically to polling.
Example:
ltc4100@9 {
compatible = "lltc,ltc4100", "sbs,sbs-charger";
reg = <0x9>;
interrupt-parent = <&gpio6>;
interrupts = <7 IRQ_TYPE_LEVEL_LOW>;
};
......@@ -8,8 +8,10 @@ Optional properties :
- interrupts : Specify the interrupt to be used to trigger when the AC
adapter is either plugged in or removed.
- ti,ac-detect-gpios : This GPIO is optionally used to read the AC adapter
presence. This is a Host GPIO that is configured as an input and
connected to the bq24735.
status. This is a Host GPIO that is configured as an input and connected
to the ACOK pin on the bq24735. Note: for backwards compatibility reasons,
the GPIO must be active on AC adapter absence despite ACOK being active
(high) on AC adapter presence.
- ti,charge-current : Used to control and set the charging current. This value
must be between 128mA and 8.128A with a 64mA step resolution. The POR value
is 0x0000h. This number is in mA (e.g. 8192), see spec for more information
......@@ -25,6 +27,8 @@ Optional properties :
- ti,external-control : Indicates that the charger is configured externally
and that the host should not attempt to enable/disable charging or set the
charge voltage/current.
- poll-interval : In case 'interrupts' is not specified, poll AC adapter
presence with this interval (milliseconds).
Example:
......
Maxim MAX14656 / AL32 USB Charger Detector
Required properties :
- compatible : "maxim,max14656";
- reg: i2c slave address
- interrupt-parent: the phandle for the interrupt controller
- interrupts: interrupt line
Example:
&i2c2 {
clock-frequency = <50000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c2>;
status = "okay";
max14656@35 {
compatible = "maxim,max14656";
reg = <0x35>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_charger_detect>;
interrupt-parent = <&gpio6>;
interrupts = <26 IRQ_TYPE_LEVEL_HIGH>;
};
};
......@@ -32,7 +32,7 @@ config POWER_RESET_AT91_RESET
config POWER_RESET_AT91_SAMA5D2_SHDWC
tristate "Atmel AT91 SAMA5D2-Compatible shutdown controller driver"
depends on ARCH_AT91 || COMPILE_TEST
depends on ARCH_AT91
default SOC_SAMA5
help
This driver supports the alternate shutdown controller for some Atmel
......
......@@ -14,9 +14,12 @@
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/printk.h>
#include <soc/at91/at91sam9_ddrsdr.h>
#define AT91_SHDW_CR 0x00 /* Shut Down Control Register */
#define AT91_SHDW_SHDW BIT(0) /* Shut Down command */
#define AT91_SHDW_KEY (0xa5 << 24) /* KEY Password */
......@@ -50,6 +53,7 @@ static const char *shdwc_wakeup_modes[] = {
static void __iomem *at91_shdwc_base;
static struct clk *sclk;
static void __iomem *mpddrc_base;
static void __init at91_wakeup_status(void)
{
......@@ -73,6 +77,29 @@ static void at91_poweroff(void)
writel(AT91_SHDW_KEY | AT91_SHDW_SHDW, at91_shdwc_base + AT91_SHDW_CR);
}
static void at91_lpddr_poweroff(void)
{
asm volatile(
/* Align to cache lines */
".balign 32\n\t"
/* Ensure AT91_SHDW_CR is in the TLB by reading it */
" ldr r6, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"
/* Power down SDRAM0 */
" str %1, [%0, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t"
/* Shutdown CPU */
" str %3, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"
" b .\n\t"
:
: "r" (mpddrc_base),
"r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF),
"r" (at91_shdwc_base),
"r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW)
: "r0");
}
static int at91_poweroff_get_wakeup_mode(struct device_node *np)
{
const char *pm;
......@@ -124,6 +151,8 @@ static void at91_poweroff_dt_set_wakeup_mode(struct platform_device *pdev)
static int __init at91_poweroff_probe(struct platform_device *pdev)
{
struct resource *res;
struct device_node *np;
u32 ddr_type;
int ret;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
......@@ -150,12 +179,30 @@ static int __init at91_poweroff_probe(struct platform_device *pdev)
pm_power_off = at91_poweroff;
np = of_find_compatible_node(NULL, NULL, "atmel,sama5d3-ddramc");
if (!np)
return 0;
mpddrc_base = of_iomap(np, 0);
of_node_put(np);
if (!mpddrc_base)
return 0;
ddr_type = readl(mpddrc_base + AT91_DDRSDRC_MDR) & AT91_DDRSDRC_MD;
if ((ddr_type == AT91_DDRSDRC_MD_LPDDR2) ||
(ddr_type == AT91_DDRSDRC_MD_LPDDR3))
pm_power_off = at91_lpddr_poweroff;
else
iounmap(mpddrc_base);
return 0;
}
static int __exit at91_poweroff_remove(struct platform_device *pdev)
{
if (pm_power_off == at91_poweroff)
if (pm_power_off == at91_poweroff ||
pm_power_off == at91_lpddr_poweroff)
pm_power_off = NULL;
clk_disable_unprepare(sclk);
......@@ -163,6 +210,11 @@ static int __exit at91_poweroff_remove(struct platform_device *pdev)
return 0;
}
static const struct of_device_id at91_ramc_of_match[] = {
{ .compatible = "atmel,sama5d3-ddramc", },
{ /* sentinel */ }
};
static const struct of_device_id at91_poweroff_of_match[] = {
{ .compatible = "atmel,at91sam9260-shdwc", },
{ .compatible = "atmel,at91sam9rl-shdwc", },
......
......@@ -134,6 +134,15 @@ static int sama5d3_restart(struct notifier_block *this, unsigned long mode,
return NOTIFY_DONE;
}
static int samx7_restart(struct notifier_block *this, unsigned long mode,
void *cmd)
{
writel(cpu_to_le32(AT91_RSTC_KEY | AT91_RSTC_PROCRST),
at91_rstc_base);
return NOTIFY_DONE;
}
static void __init at91_reset_status(struct platform_device *pdev)
{
u32 reg = readl(at91_rstc_base + AT91_RSTC_SR);
......@@ -173,6 +182,7 @@ static const struct of_device_id at91_reset_of_match[] = {
{ .compatible = "atmel,at91sam9260-rstc", .data = at91sam9260_restart },
{ .compatible = "atmel,at91sam9g45-rstc", .data = at91sam9g45_restart },
{ .compatible = "atmel,sama5d3-rstc", .data = sama5d3_restart },
{ .compatible = "atmel,samx7-rstc", .data = samx7_restart },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, at91_reset_of_match);
......@@ -238,20 +248,12 @@ static int __exit at91_reset_remove(struct platform_device *pdev)
return 0;
}
static const struct platform_device_id at91_reset_plat_match[] = {
{ "at91-sam9260-reset", (unsigned long)at91sam9260_restart },
{ "at91-sam9g45-reset", (unsigned long)at91sam9g45_restart },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(platform, at91_reset_plat_match);
static struct platform_driver at91_reset_driver = {
.remove = __exit_p(at91_reset_remove),
.driver = {
.name = "at91-reset",
.of_match_table = at91_reset_of_match,
},
.id_table = at91_reset_plat_match,
};
module_platform_driver_probe(at91_reset_driver, at91_reset_probe);
......
......@@ -22,9 +22,12 @@
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/printk.h>
#include <soc/at91/at91sam9_ddrsdr.h>
#define SLOW_CLOCK_FREQ 32768
#define AT91_SHDW_CR 0x00 /* Shut Down Control Register */
......@@ -75,6 +78,7 @@ struct shdwc {
*/
static struct shdwc *at91_shdwc;
static struct clk *sclk;
static void __iomem *mpddrc_base;
static const unsigned long long sdwc_dbc_period[] = {
0, 3, 32, 512, 4096, 32768,
......@@ -108,6 +112,29 @@ static void at91_poweroff(void)
at91_shdwc->at91_shdwc_base + AT91_SHDW_CR);
}
static void at91_lpddr_poweroff(void)
{
asm volatile(
/* Align to cache lines */
".balign 32\n\t"
/* Ensure AT91_SHDW_CR is in the TLB by reading it */
" ldr r6, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"
/* Power down SDRAM0 */
" str %1, [%0, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t"
/* Shutdown CPU */
" str %3, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"
" b .\n\t"
:
: "r" (mpddrc_base),
"r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF),
"r" (at91_shdwc->at91_shdwc_base),
"r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW)
: "r0");
}
static u32 at91_shdwc_debouncer_value(struct platform_device *pdev,
u32 in_period_us)
{
......@@ -212,6 +239,8 @@ static int __init at91_shdwc_probe(struct platform_device *pdev)
{
struct resource *res;
const struct of_device_id *match;
struct device_node *np;
u32 ddr_type;
int ret;
if (!pdev->dev.of_node)
......@@ -249,6 +278,23 @@ static int __init at91_shdwc_probe(struct platform_device *pdev)
pm_power_off = at91_poweroff;
np = of_find_compatible_node(NULL, NULL, "atmel,sama5d3-ddramc");
if (!np)
return 0;
mpddrc_base = of_iomap(np, 0);
of_node_put(np);
if (!mpddrc_base)
return 0;
ddr_type = readl(mpddrc_base + AT91_DDRSDRC_MDR) & AT91_DDRSDRC_MD;
if ((ddr_type == AT91_DDRSDRC_MD_LPDDR2) ||
(ddr_type == AT91_DDRSDRC_MD_LPDDR3))
pm_power_off = at91_lpddr_poweroff;
else
iounmap(mpddrc_base);
return 0;
}
......@@ -256,7 +302,8 @@ static int __exit at91_shdwc_remove(struct platform_device *pdev)
{
struct shdwc *shdw = platform_get_drvdata(pdev);
if (pm_power_off == at91_poweroff)
if (pm_power_off == at91_poweroff ||
pm_power_off == at91_lpddr_poweroff)
pm_power_off = NULL;
/* Reset values to disable wake-up features */
......
......@@ -164,6 +164,12 @@ config BATTERY_SBS
Say Y to include support for SBS battery driver for SBS-compliant
gas gauges.
config CHARGER_SBS
tristate "SBS Compliant charger"
depends on I2C
help
Say Y to include support for SBS compilant battery chargers.
config BATTERY_BQ27XXX
tristate "BQ27xxx battery driver"
help
......@@ -214,6 +220,18 @@ config BATTERY_DA9150
This driver can also be built as a module. If so, the module will be
called da9150-fg.
config CHARGER_AXP20X
tristate "X-Powers AXP20X and AXP22X AC power supply driver"
depends on MFD_AXP20X
depends on AXP20X_ADC
depends on IIO
help
Say Y here to enable support for X-Powers AXP20X and AXP22X PMICs' AC
power supply.
This driver can also be built as a module. If so, the module will be
called axp20x_ac_power.
config AXP288_CHARGER
tristate "X-Powers AXP288 Charger"
depends on MFD_AXP20X && EXTCON_AXP288
......@@ -292,13 +310,6 @@ config BATTERY_JZ4740
This driver can be build as a module. If so, the module will be
called jz4740-battery.
config BATTERY_INTEL_MID
tristate "Battery driver for Intel MID platforms"
depends on INTEL_SCU_IPC && SPI
help
Say Y here to enable the battery driver on Intel MID
platforms.
config BATTERY_RX51
tristate "Nokia RX-51 (N900) battery driver"
depends on TWL4030_MADC
......@@ -370,6 +381,16 @@ config CHARGER_MAX14577
Say Y to enable support for the battery charger control sysfs and
platform data of MAX14577/77836 MUICs.
config CHARGER_DETECTOR_MAX14656
tristate "Maxim MAX14656 USB charger detector"
depends on I2C
depends on OF
help
Say Y to enable support for the Maxim MAX14656 USB charger detector.
The device is compliant with the USB Battery Charging Specification
Revision 1.2 and can be found e.g. in Kindle 4/5th generation
readers and certain LG devices.
config CHARGER_MAX77693
tristate "Maxim MAX77693 battery charger driver"
depends on MFD_MAX77693
......@@ -395,6 +416,7 @@ config CHARGER_QCOM_SMBB
depends on MFD_SPMI_PMIC || COMPILE_TEST
depends on OF
depends on EXTCON
depends on REGULATOR
help
Say Y to include support for the Switch-Mode Battery Charger and
Boost (SMBB) hardware found in Qualcomm PM8941 PMICs. The charger
......
......@@ -18,6 +18,7 @@ obj-$(CONFIG_TEST_POWER) += test_power.o
obj-$(CONFIG_BATTERY_88PM860X) += 88pm860x_battery.o
obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o
obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o
obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o
obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o
obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o
......@@ -31,6 +32,7 @@ obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o
obj-$(CONFIG_BATTERY_IPAQ_MICRO) += ipaq_micro_battery.o
obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o
obj-$(CONFIG_BATTERY_SBS) += sbs-battery.o
obj-$(CONFIG_CHARGER_SBS) += sbs-charger.o
obj-$(CONFIG_BATTERY_BQ27XXX) += bq27xxx_battery.o
obj-$(CONFIG_BATTERY_BQ27XXX_I2C) += bq27xxx_battery_i2c.o
obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o
......@@ -47,7 +49,6 @@ obj-$(CONFIG_BATTERY_TWL4030_MADC) += twl4030_madc_battery.o
obj-$(CONFIG_CHARGER_88PM860X) += 88pm860x_charger.o
obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o
obj-$(CONFIG_BATTERY_INTEL_MID) += intel_mid_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_ISP1704) += isp1704_charger.o
......@@ -58,6 +59,7 @@ obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o
obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o
obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o
obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o
obj-$(CONFIG_CHARGER_DETECTOR_MAX14656) += max14656_charger_detector.o
obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o
obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o
obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o
......
......@@ -76,8 +76,8 @@ struct ab8500_btemp_ranges {
* @dev: Pointer to the structure device
* @node: List of AB8500 BTEMPs, hence prepared for reentrance
* @curr_source: What current source we use, in uA
* @bat_temp: Dispatched battery temperature in degree Celcius
* @prev_bat_temp Last measured battery temperature in degree Celcius
* @bat_temp: Dispatched battery temperature in degree Celsius
* @prev_bat_temp Last measured battery temperature in degree Celsius
* @parent: Pointer to the struct ab8500
* @gpadc: Pointer to the struct gpadc
* @fg: Pointer to the struct fg
......@@ -123,10 +123,7 @@ static LIST_HEAD(ab8500_btemp_list);
*/
struct ab8500_btemp *ab8500_btemp_get(void)
{
struct ab8500_btemp *btemp;
btemp = list_first_entry(&ab8500_btemp_list, struct ab8500_btemp, node);
return btemp;
return list_first_entry(&ab8500_btemp_list, struct ab8500_btemp, node);
}
EXPORT_SYMBOL(ab8500_btemp_get);
......@@ -464,13 +461,13 @@ static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di)
* @tbl_size: size of the resistance to temperature table
* @res: resistance to calculate the temperature from
*
* This function returns the battery temperature in degrees Celcius
* This function returns the battery temperature in degrees Celsius
* based on the NTC resistance.
*/
static int ab8500_btemp_res_to_temp(struct ab8500_btemp *di,
const struct abx500_res_to_temp *tbl, int tbl_size, int res)
{
int i, temp;
int i;
/*
* Calculate the formula for the straight line
* Simple interpolation if we are within
......@@ -488,9 +485,8 @@ static int ab8500_btemp_res_to_temp(struct ab8500_btemp *di,
i++;
}
temp = tbl[i].temp + ((tbl[i + 1].temp - tbl[i].temp) *
return tbl[i].temp + ((tbl[i + 1].temp - tbl[i].temp) *
(res - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist);
return temp;
}
/**
......
/*
* AXP20X and AXP22X PMICs' ACIN power supply driver
*
* Copyright (C) 2016 Free Electrons
* Quentin Schulz <quentin.schulz@free-electrons.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/device.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/mfd/axp20x.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/iio/consumer.h>
#define AXP20X_PWR_STATUS_ACIN_PRESENT BIT(7)
#define AXP20X_PWR_STATUS_ACIN_AVAIL BIT(6)
#define DRVNAME "axp20x-ac-power-supply"
struct axp20x_ac_power {
struct regmap *regmap;
struct power_supply *supply;
struct iio_channel *acin_v;
struct iio_channel *acin_i;
};
static irqreturn_t axp20x_ac_power_irq(int irq, void *devid)
{
struct axp20x_ac_power *power = devid;
power_supply_changed(power->supply);
return IRQ_HANDLED;
}
static int axp20x_ac_power_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct axp20x_ac_power *power = power_supply_get_drvdata(psy);
int ret, reg;
switch (psp) {
case POWER_SUPPLY_PROP_HEALTH:
ret = regmap_read(power->regmap, AXP20X_PWR_INPUT_STATUS, &reg);
if (ret)
return ret;
if (reg & AXP20X_PWR_STATUS_ACIN_PRESENT) {
val->intval = POWER_SUPPLY_HEALTH_GOOD;
return 0;
}
val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
return 0;
case POWER_SUPPLY_PROP_PRESENT:
ret = regmap_read(power->regmap, AXP20X_PWR_INPUT_STATUS, &reg);
if (ret)
return ret;
val->intval = !!(reg & AXP20X_PWR_STATUS_ACIN_PRESENT);
return 0;
case POWER_SUPPLY_PROP_ONLINE:
ret = regmap_read(power->regmap, AXP20X_PWR_INPUT_STATUS, &reg);
if (ret)
return ret;
val->intval = !!(reg & AXP20X_PWR_STATUS_ACIN_AVAIL);
return 0;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
ret = iio_read_channel_processed(power->acin_v, &val->intval);
if (ret)
return ret;
/* IIO framework gives mV but Power Supply framework gives uV */
val->intval *= 1000;
return 0;
case POWER_SUPPLY_PROP_CURRENT_NOW:
ret = iio_read_channel_processed(power->acin_i, &val->intval);
if (ret)
return ret;
/* IIO framework gives mA but Power Supply framework gives uA */
val->intval *= 1000;
return 0;
default:
return -EINVAL;
}
return -EINVAL;
}
static enum power_supply_property axp20x_ac_power_properties[] = {
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
};
static enum power_supply_property axp22x_ac_power_properties[] = {
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_ONLINE,
};
static const struct power_supply_desc axp20x_ac_power_desc = {
.name = "axp20x-ac",
.type = POWER_SUPPLY_TYPE_MAINS,
.properties = axp20x_ac_power_properties,
.num_properties = ARRAY_SIZE(axp20x_ac_power_properties),
.get_property = axp20x_ac_power_get_property,
};
static const struct power_supply_desc axp22x_ac_power_desc = {
.name = "axp22x-ac",
.type = POWER_SUPPLY_TYPE_MAINS,
.properties = axp22x_ac_power_properties,
.num_properties = ARRAY_SIZE(axp22x_ac_power_properties),
.get_property = axp20x_ac_power_get_property,
};
struct axp_data {
const struct power_supply_desc *power_desc;
bool acin_adc;
};
static const struct axp_data axp20x_data = {
.power_desc = &axp20x_ac_power_desc,
.acin_adc = true,
};
static const struct axp_data axp22x_data = {
.power_desc = &axp22x_ac_power_desc,
.acin_adc = false,
};
static int axp20x_ac_power_probe(struct platform_device *pdev)
{
struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
struct power_supply_config psy_cfg = {};
struct axp20x_ac_power *power;
struct axp_data *axp_data;
static const char * const irq_names[] = { "ACIN_PLUGIN", "ACIN_REMOVAL",
NULL };
int i, irq, ret;
if (!of_device_is_available(pdev->dev.of_node))
return -ENODEV;
if (!axp20x) {
dev_err(&pdev->dev, "Parent drvdata not set\n");
return -EINVAL;
}
power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL);
if (!power)
return -ENOMEM;
axp_data = (struct axp_data *)of_device_get_match_data(&pdev->dev);
if (axp_data->acin_adc) {
power->acin_v = devm_iio_channel_get(&pdev->dev, "acin_v");
if (IS_ERR(power->acin_v)) {
if (PTR_ERR(power->acin_v) == -ENODEV)
return -EPROBE_DEFER;
return PTR_ERR(power->acin_v);
}
power->acin_i = devm_iio_channel_get(&pdev->dev, "acin_i");
if (IS_ERR(power->acin_i)) {
if (PTR_ERR(power->acin_i) == -ENODEV)
return -EPROBE_DEFER;
return PTR_ERR(power->acin_i);
}
}
power->regmap = dev_get_regmap(pdev->dev.parent, NULL);
platform_set_drvdata(pdev, power);
psy_cfg.of_node = pdev->dev.of_node;
psy_cfg.drv_data = power;
power->supply = devm_power_supply_register(&pdev->dev,
axp_data->power_desc,
&psy_cfg);
if (IS_ERR(power->supply))
return PTR_ERR(power->supply);
/* Request irqs after registering, as irqs may trigger immediately */
for (i = 0; irq_names[i]; i++) {
irq = platform_get_irq_byname(pdev, irq_names[i]);
if (irq < 0) {
dev_warn(&pdev->dev, "No IRQ for %s: %d\n",
irq_names[i], irq);
continue;
}
irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq);
ret = devm_request_any_context_irq(&pdev->dev, irq,
axp20x_ac_power_irq, 0,
DRVNAME, power);
if (ret < 0)
dev_warn(&pdev->dev, "Error requesting %s IRQ: %d\n",
irq_names[i], ret);
}
return 0;
}
static const struct of_device_id axp20x_ac_power_match[] = {
{
.compatible = "x-powers,axp202-ac-power-supply",
.data = (void *)&axp20x_data,
}, {
.compatible = "x-powers,axp221-ac-power-supply",
.data = (void *)&axp22x_data,
}, { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, axp20x_ac_power_match);
static struct platform_driver axp20x_ac_power_driver = {
.probe = axp20x_ac_power_probe,
.driver = {
.name = DRVNAME,
.of_match_table = axp20x_ac_power_match,
},
};
module_platform_driver(axp20x_ac_power_driver);
MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
MODULE_DESCRIPTION("AXP20X and AXP22X PMICs' AC power supply driver");
MODULE_LICENSE("GPL");
......@@ -17,10 +17,12 @@
#include <linux/mfd/axp20x.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/iio/consumer.h>
#define DRVNAME "axp20x-usb-power-supply"
......@@ -30,6 +32,8 @@
#define AXP20X_USB_STATUS_VBUS_VALID BIT(2)
#define AXP20X_VBUS_VHOLD_uV(b) (4000000 + (((b) >> 3) & 7) * 100000)
#define AXP20X_VBUS_VHOLD_MASK GENMASK(5, 3)
#define AXP20X_VBUS_VHOLD_OFFSET 3
#define AXP20X_VBUS_CLIMIT_MASK 3
#define AXP20X_VBUC_CLIMIT_900mA 0
#define AXP20X_VBUC_CLIMIT_500mA 1
......@@ -45,6 +49,9 @@ struct axp20x_usb_power {
struct device_node *np;
struct regmap *regmap;
struct power_supply *supply;
enum axp20x_variants axp20x_id;
struct iio_channel *vbus_v;
struct iio_channel *vbus_i;
};
static irqreturn_t axp20x_usb_power_irq(int irq, void *devid)
......@@ -72,6 +79,20 @@ static int axp20x_usb_power_get_property(struct power_supply *psy,
val->intval = AXP20X_VBUS_VHOLD_uV(v);
return 0;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
if (IS_ENABLED(CONFIG_AXP20X_ADC)) {
ret = iio_read_channel_processed(power->vbus_v,
&val->intval);
if (ret)
return ret;
/*
* IIO framework gives mV but Power Supply framework
* gives uV.
*/
val->intval *= 1000;
return 0;
}
ret = axp20x_read_variable_width(power->regmap,
AXP20X_VBUS_V_ADC_H, 12);
if (ret < 0)
......@@ -86,12 +107,10 @@ static int axp20x_usb_power_get_property(struct power_supply *psy,
switch (v & AXP20X_VBUS_CLIMIT_MASK) {
case AXP20X_VBUC_CLIMIT_100mA:
if (of_device_is_compatible(power->np,
"x-powers,axp202-usb-power-supply")) {
val->intval = 100000;
} else {
if (power->axp20x_id == AXP221_ID)
val->intval = -1; /* No 100mA limit */
}
else
val->intval = 100000;
break;
case AXP20X_VBUC_CLIMIT_500mA:
val->intval = 500000;
......@@ -105,6 +124,20 @@ static int axp20x_usb_power_get_property(struct power_supply *psy,
}
return 0;
case POWER_SUPPLY_PROP_CURRENT_NOW:
if (IS_ENABLED(CONFIG_AXP20X_ADC)) {
ret = iio_read_channel_processed(power->vbus_i,
&val->intval);
if (ret)
return ret;
/*
* IIO framework gives mA but Power Supply framework
* gives uA.
*/
val->intval *= 1000;
return 0;
}
ret = axp20x_read_variable_width(power->regmap,
AXP20X_VBUS_I_ADC_H, 12);
if (ret < 0)
......@@ -130,8 +163,7 @@ static int axp20x_usb_power_get_property(struct power_supply *psy,
val->intval = POWER_SUPPLY_HEALTH_GOOD;
if (of_device_is_compatible(power->np,
"x-powers,axp202-usb-power-supply")) {
if (power->axp20x_id == AXP202_ID) {
ret = regmap_read(power->regmap,
AXP20X_USB_OTG_STATUS, &v);
if (ret)
......@@ -155,6 +187,81 @@ static int axp20x_usb_power_get_property(struct power_supply *psy,
return 0;
}
static int axp20x_usb_power_set_voltage_min(struct axp20x_usb_power *power,
int intval)
{
int val;
switch (intval) {
case 4000000:
case 4100000:
case 4200000:
case 4300000:
case 4400000:
case 4500000:
case 4600000:
case 4700000:
val = (intval - 4000000) / 100000;
return regmap_update_bits(power->regmap,
AXP20X_VBUS_IPSOUT_MGMT,
AXP20X_VBUS_VHOLD_MASK,
val << AXP20X_VBUS_VHOLD_OFFSET);
default:
return -EINVAL;
}
return -EINVAL;
}
static int axp20x_usb_power_set_current_max(struct axp20x_usb_power *power,
int intval)
{
int val;
switch (intval) {
case 100000:
if (power->axp20x_id == AXP221_ID)
return -EINVAL;
case 500000:
case 900000:
val = (900000 - intval) / 400000;
return regmap_update_bits(power->regmap,
AXP20X_VBUS_IPSOUT_MGMT,
AXP20X_VBUS_CLIMIT_MASK, val);
default:
return -EINVAL;
}
return -EINVAL;
}
static int axp20x_usb_power_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct axp20x_usb_power *power = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_VOLTAGE_MIN:
return axp20x_usb_power_set_voltage_min(power, val->intval);
case POWER_SUPPLY_PROP_CURRENT_MAX:
return axp20x_usb_power_set_current_max(power, val->intval);
default:
return -EINVAL;
}
return -EINVAL;
}
static int axp20x_usb_power_prop_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN ||
psp == POWER_SUPPLY_PROP_CURRENT_MAX;
}
static enum power_supply_property axp20x_usb_power_properties[] = {
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT,
......@@ -178,7 +285,9 @@ static const struct power_supply_desc axp20x_usb_power_desc = {
.type = POWER_SUPPLY_TYPE_USB,
.properties = axp20x_usb_power_properties,
.num_properties = ARRAY_SIZE(axp20x_usb_power_properties),
.property_is_writeable = axp20x_usb_power_prop_writeable,
.get_property = axp20x_usb_power_get_property,
.set_property = axp20x_usb_power_set_property,
};
static const struct power_supply_desc axp22x_usb_power_desc = {
......@@ -186,9 +295,41 @@ static const struct power_supply_desc axp22x_usb_power_desc = {
.type = POWER_SUPPLY_TYPE_USB,
.properties = axp22x_usb_power_properties,
.num_properties = ARRAY_SIZE(axp22x_usb_power_properties),
.property_is_writeable = axp20x_usb_power_prop_writeable,
.get_property = axp20x_usb_power_get_property,
.set_property = axp20x_usb_power_set_property,
};
static int configure_iio_channels(struct platform_device *pdev,
struct axp20x_usb_power *power)
{
power->vbus_v = devm_iio_channel_get(&pdev->dev, "vbus_v");
if (IS_ERR(power->vbus_v)) {
if (PTR_ERR(power->vbus_v) == -ENODEV)
return -EPROBE_DEFER;
return PTR_ERR(power->vbus_v);
}
power->vbus_i = devm_iio_channel_get(&pdev->dev, "vbus_i");
if (IS_ERR(power->vbus_i)) {
if (PTR_ERR(power->vbus_i) == -ENODEV)
return -EPROBE_DEFER;
return PTR_ERR(power->vbus_i);
}
return 0;
}
static int configure_adc_registers(struct axp20x_usb_power *power)
{
/* Enable vbus voltage and current measurement */
return regmap_update_bits(power->regmap, AXP20X_ADC_EN1,
AXP20X_ADC_EN1_VBUS_CURR |
AXP20X_ADC_EN1_VBUS_VOLT,
AXP20X_ADC_EN1_VBUS_CURR |
AXP20X_ADC_EN1_VBUS_VOLT);
}
static int axp20x_usb_power_probe(struct platform_device *pdev)
{
struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
......@@ -214,11 +355,13 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
if (!power)
return -ENOMEM;
power->axp20x_id = (enum axp20x_variants)of_device_get_match_data(
&pdev->dev);
power->np = pdev->dev.of_node;
power->regmap = axp20x->regmap;
if (of_device_is_compatible(power->np,
"x-powers,axp202-usb-power-supply")) {
if (power->axp20x_id == AXP202_ID) {
/* Enable vbus valid checking */
ret = regmap_update_bits(power->regmap, AXP20X_VBUS_MON,
AXP20X_VBUS_MON_VBUS_VALID,
......@@ -226,17 +369,18 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
if (ret)
return ret;
/* Enable vbus voltage and current measurement */
ret = regmap_update_bits(power->regmap, AXP20X_ADC_EN1,
AXP20X_ADC_EN1_VBUS_CURR | AXP20X_ADC_EN1_VBUS_VOLT,
AXP20X_ADC_EN1_VBUS_CURR | AXP20X_ADC_EN1_VBUS_VOLT);
if (IS_ENABLED(CONFIG_AXP20X_ADC))
ret = configure_iio_channels(pdev, power);
else
ret = configure_adc_registers(power);
if (ret)
return ret;
usb_power_desc = &axp20x_usb_power_desc;
irq_names = axp20x_irq_names;
} else if (of_device_is_compatible(power->np,
"x-powers,axp221-usb-power-supply")) {
} else if (power->axp20x_id == AXP221_ID ||
power->axp20x_id == AXP223_ID) {
usb_power_desc = &axp22x_usb_power_desc;
irq_names = axp22x_irq_names;
} else {
......@@ -273,9 +417,16 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
}
static const struct of_device_id axp20x_usb_power_match[] = {
{ .compatible = "x-powers,axp202-usb-power-supply" },
{ .compatible = "x-powers,axp221-usb-power-supply" },
{ }
{
.compatible = "x-powers,axp202-usb-power-supply",
.data = (void *)AXP202_ID,
}, {
.compatible = "x-powers,axp221-usb-power-supply",
.data = (void *)AXP221_ID,
}, {
.compatible = "x-powers,axp223-usb-power-supply",
.data = (void *)AXP223_ID,
}, { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, axp20x_usb_power_match);
......
......@@ -90,20 +90,6 @@
#define CHRG_VLTFC_0C 0xA5 /* 0 DegC */
#define CHRG_VHTFC_45C 0x1F /* 45 DegC */
#define BAT_IRQ_CFG_CHRG_DONE (1 << 2)
#define BAT_IRQ_CFG_CHRG_START (1 << 3)
#define BAT_IRQ_CFG_BAT_SAFE_EXIT (1 << 4)
#define BAT_IRQ_CFG_BAT_SAFE_ENTER (1 << 5)
#define BAT_IRQ_CFG_BAT_DISCON (1 << 6)
#define BAT_IRQ_CFG_BAT_CONN (1 << 7)
#define BAT_IRQ_CFG_BAT_MASK 0xFC
#define TEMP_IRQ_CFG_QCBTU (1 << 4)
#define TEMP_IRQ_CFG_CBTU (1 << 5)
#define TEMP_IRQ_CFG_QCBTO (1 << 6)
#define TEMP_IRQ_CFG_CBTO (1 << 7)
#define TEMP_IRQ_CFG_MASK 0xF0
#define FG_CNTL_OCV_ADJ_EN (1 << 3)
#define CV_4100MV 4100 /* 4100mV */
......@@ -127,6 +113,10 @@
#define ILIM_3000MA 3000 /* 3000mA */
#define AXP288_EXTCON_DEV_NAME "axp288_extcon"
#define USB_HOST_EXTCON_DEV_NAME "INT3496:00"
static const unsigned int cable_ids[] =
{ EXTCON_CHG_USB_SDP, EXTCON_CHG_USB_CDP, EXTCON_CHG_USB_DCP };
enum {
VBUS_OV_IRQ = 0,
......@@ -143,7 +133,6 @@ enum {
struct axp288_chrg_info {
struct platform_device *pdev;
struct axp20x_chrg_pdata *pdata;
struct regmap *regmap;
struct regmap_irq_chip_data *regmap_irqc;
int irq[CHRG_INTR_END];
......@@ -163,20 +152,16 @@ struct axp288_chrg_info {
struct extcon_dev *edev;
bool connected;
enum power_supply_type chg_type;
struct notifier_block nb;
struct notifier_block nb[ARRAY_SIZE(cable_ids)];
struct work_struct work;
} cable;
int health;
int inlmt;
int cc;
int cv;
int max_cc;
int max_cv;
bool online;
bool present;
bool enable_charger;
bool is_charger_enabled;
int is_charger_enabled;
};
static inline int axp288_charger_set_cc(struct axp288_chrg_info *info, int cc)
......@@ -305,6 +290,9 @@ static int axp288_charger_enable_charger(struct axp288_chrg_info *info,
{
int ret;
if ((int)enable == info->is_charger_enabled)
return 0;
if (enable)
ret = regmap_update_bits(info->regmap, AXP20X_CHRG_CTRL1,
CHRG_CCCV_CHG_EN, CHRG_CCCV_CHG_EN);
......@@ -430,8 +418,7 @@ static int axp288_charger_usb_get_property(struct power_supply *psy,
ret = axp288_charger_is_present(info);
if (ret < 0)
goto psy_get_prop_fail;
info->present = ret;
val->intval = info->present;
val->intval = ret;
break;
case POWER_SUPPLY_PROP_ONLINE:
/* Check for OTG case first */
......@@ -442,8 +429,7 @@ static int axp288_charger_usb_get_property(struct power_supply *psy,
ret = axp288_charger_is_online(info);
if (ret < 0)
goto psy_get_prop_fail;
info->online = ret;
val->intval = info->online;
val->intval = ret;
break;
case POWER_SUPPLY_PROP_HEALTH:
val->intval = axp288_get_charger_health(info);
......@@ -576,20 +562,20 @@ static void axp288_charger_extcon_evt_worker(struct work_struct *work)
struct axp288_chrg_info *info =
container_of(work, struct axp288_chrg_info, cable.work);
int ret, current_limit;
bool changed = false;
struct extcon_dev *edev = info->cable.edev;
bool old_connected = info->cable.connected;
enum power_supply_type old_chg_type = info->cable.chg_type;
/* Determine cable/charger type */
if (extcon_get_cable_state_(edev, EXTCON_CHG_USB_SDP) > 0) {
if (extcon_get_state(edev, EXTCON_CHG_USB_SDP) > 0) {
dev_dbg(&info->pdev->dev, "USB SDP charger is connected");
info->cable.connected = true;
info->cable.chg_type = POWER_SUPPLY_TYPE_USB;
} else if (extcon_get_cable_state_(edev, EXTCON_CHG_USB_CDP) > 0) {
} else if (extcon_get_state(edev, EXTCON_CHG_USB_CDP) > 0) {
dev_dbg(&info->pdev->dev, "USB CDP charger is connected");
info->cable.connected = true;
info->cable.chg_type = POWER_SUPPLY_TYPE_USB_CDP;
} else if (extcon_get_cable_state_(edev, EXTCON_CHG_USB_DCP) > 0) {
} else if (extcon_get_state(edev, EXTCON_CHG_USB_DCP) > 0) {
dev_dbg(&info->pdev->dev, "USB DCP charger is connected");
info->cable.connected = true;
info->cable.chg_type = POWER_SUPPLY_TYPE_USB_DCP;
......@@ -601,22 +587,15 @@ static void axp288_charger_extcon_evt_worker(struct work_struct *work)
}
/* Cable status changed */
if (old_connected != info->cable.connected)
changed = true;
if (!changed)
if (old_connected == info->cable.connected &&
old_chg_type == info->cable.chg_type)
return;
mutex_lock(&info->lock);
if (info->is_charger_enabled && !info->cable.connected) {
info->enable_charger = false;
ret = axp288_charger_enable_charger(info, info->enable_charger);
if (ret < 0)
dev_err(&info->pdev->dev,
"cannot disable charger (%d)", ret);
if (info->cable.connected) {
axp288_charger_enable_charger(info, false);
} else if (!info->is_charger_enabled && info->cable.connected) {
switch (info->cable.chg_type) {
case POWER_SUPPLY_TYPE_USB:
current_limit = ILIM_500MA;
......@@ -635,36 +614,49 @@ static void axp288_charger_extcon_evt_worker(struct work_struct *work)
/* Set vbus current limit first, then enable charger */
ret = axp288_charger_set_vbus_inlmt(info, current_limit);
if (ret < 0) {
if (ret == 0)
axp288_charger_enable_charger(info, true);
else
dev_err(&info->pdev->dev,
"error setting current limit (%d)", ret);
} else {
info->enable_charger = (current_limit > 0);
ret = axp288_charger_enable_charger(info,
info->enable_charger);
if (ret < 0)
dev_err(&info->pdev->dev,
"cannot enable charger (%d)", ret);
axp288_charger_enable_charger(info, false);
}
}
if (changed)
info->health = axp288_get_charger_health(info);
mutex_unlock(&info->lock);
if (changed)
power_supply_changed(info->psy_usb);
}
static int axp288_charger_handle_cable_evt(struct notifier_block *nb,
/*
* We need 3 copies of this, because there is no way to find out for which
* cable id we are being called from the passed in arguments; and we must
* have a separate nb for each extcon_register_notifier call.
*/
static int axp288_charger_handle_cable0_evt(struct notifier_block *nb,
unsigned long event, void *param)
{
struct axp288_chrg_info *info =
container_of(nb, struct axp288_chrg_info, cable.nb);
container_of(nb, struct axp288_chrg_info, cable.nb[0]);
schedule_work(&info->cable.work);
return NOTIFY_OK;
}
static int axp288_charger_handle_cable1_evt(struct notifier_block *nb,
unsigned long event, void *param)
{
struct axp288_chrg_info *info =
container_of(nb, struct axp288_chrg_info, cable.nb[1]);
schedule_work(&info->cable.work);
return NOTIFY_OK;
}
static int axp288_charger_handle_cable2_evt(struct notifier_block *nb,
unsigned long event, void *param)
{
struct axp288_chrg_info *info =
container_of(nb, struct axp288_chrg_info, cable.nb[2]);
schedule_work(&info->cable.work);
return NOTIFY_OK;
}
......@@ -672,7 +664,17 @@ static void axp288_charger_otg_evt_worker(struct work_struct *work)
{
struct axp288_chrg_info *info =
container_of(work, struct axp288_chrg_info, otg.work);
int ret;
struct extcon_dev *edev = info->otg.cable;
int ret, usb_host = extcon_get_state(edev, EXTCON_USB_HOST);
dev_dbg(&info->pdev->dev, "external connector USB-Host is %s\n",
usb_host ? "attached" : "detached");
/*
* Set usb_id_short flag to avoid running charger detection logic
* in case usb host.
*/
info->otg.id_short = usb_host;
/* Disable VBUS path before enabling the 5V boost */
ret = axp288_charger_vbus_path_select(info, !info->otg.id_short);
......@@ -685,87 +687,70 @@ static int axp288_charger_handle_otg_evt(struct notifier_block *nb,
{
struct axp288_chrg_info *info =
container_of(nb, struct axp288_chrg_info, otg.id_nb);
struct extcon_dev *edev = info->otg.cable;
int usb_host = extcon_get_cable_state_(edev, EXTCON_USB_HOST);
dev_dbg(&info->pdev->dev, "external connector USB-Host is %s\n",
usb_host ? "attached" : "detached");
/*
* Set usb_id_short flag to avoid running charger detection logic
* in case usb host.
*/
info->otg.id_short = usb_host;
schedule_work(&info->otg.work);
return NOTIFY_OK;
}
static void charger_init_hw_regs(struct axp288_chrg_info *info)
static int charger_init_hw_regs(struct axp288_chrg_info *info)
{
int ret, cc, cv;
unsigned int val;
/* Program temperature thresholds */
ret = regmap_write(info->regmap, AXP20X_V_LTF_CHRG, CHRG_VLTFC_0C);
if (ret < 0)
dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
if (ret < 0) {
dev_err(&info->pdev->dev, "register(%x) write error(%d)\n",
AXP20X_V_LTF_CHRG, ret);
return ret;
}
ret = regmap_write(info->regmap, AXP20X_V_HTF_CHRG, CHRG_VHTFC_45C);
if (ret < 0)
dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
if (ret < 0) {
dev_err(&info->pdev->dev, "register(%x) write error(%d)\n",
AXP20X_V_HTF_CHRG, ret);
return ret;
}
/* Do not turn-off charger o/p after charge cycle ends */
ret = regmap_update_bits(info->regmap,
AXP20X_CHRG_CTRL2,
CNTL2_CHG_OUT_TURNON, 1);
if (ret < 0)
dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
CNTL2_CHG_OUT_TURNON, CNTL2_CHG_OUT_TURNON);
if (ret < 0) {
dev_err(&info->pdev->dev, "register(%x) write error(%d)\n",
AXP20X_CHRG_CTRL2, ret);
/* Enable interrupts */
ret = regmap_update_bits(info->regmap,
AXP20X_IRQ2_EN,
BAT_IRQ_CFG_BAT_MASK, 1);
if (ret < 0)
dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
AXP20X_IRQ2_EN, ret);
ret = regmap_update_bits(info->regmap, AXP20X_IRQ3_EN,
TEMP_IRQ_CFG_MASK, 1);
if (ret < 0)
dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
AXP20X_IRQ3_EN, ret);
return ret;
}
/* Setup ending condition for charging to be 10% of I(chrg) */
ret = regmap_update_bits(info->regmap,
AXP20X_CHRG_CTRL1,
CHRG_CCCV_ITERM_20P, 0);
if (ret < 0)
dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
if (ret < 0) {
dev_err(&info->pdev->dev, "register(%x) write error(%d)\n",
AXP20X_CHRG_CTRL1, ret);
return ret;
}
/* Disable OCV-SOC curve calibration */
ret = regmap_update_bits(info->regmap,
AXP20X_CC_CTRL,
FG_CNTL_OCV_ADJ_EN, 0);
if (ret < 0)
dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
if (ret < 0) {
dev_err(&info->pdev->dev, "register(%x) write error(%d)\n",
AXP20X_CC_CTRL, ret);
/* Init charging current and voltage */
info->max_cc = info->pdata->max_cc;
info->max_cv = info->pdata->max_cv;
return ret;
}
/* Read current charge voltage and current limit */
ret = regmap_read(info->regmap, AXP20X_CHRG_CTRL1, &val);
if (ret < 0) {
/* Assume default if cannot read */
info->cc = info->pdata->def_cc;
info->cv = info->pdata->def_cv;
} else {
dev_err(&info->pdev->dev, "register(%x) read error(%d)\n",
AXP20X_CHRG_CTRL1, ret);
return ret;
}
/* Determine charge voltage */
cv = (val & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS;
switch (cv) {
......@@ -781,9 +766,6 @@ static void charger_init_hw_regs(struct axp288_chrg_info *info)
case CHRG_CCCV_CV_4350MV:
info->cv = CV_4350MV;
break;
default:
info->cv = INT_MAX;
break;
}
/* Determine charge current limit */
......@@ -791,29 +773,23 @@ static void charger_init_hw_regs(struct axp288_chrg_info *info)
cc = (cc * CHRG_CCCV_CC_LSB_RES) + CHRG_CCCV_CC_OFFSET;
info->cc = cc;
/* Program default charging voltage and current */
cc = min(info->pdata->def_cc, info->max_cc);
cv = min(info->pdata->def_cv, info->max_cv);
ret = axp288_charger_set_cc(info, cc);
if (ret < 0)
dev_warn(&info->pdev->dev,
"error(%d) in setting CC\n", ret);
/*
* Do not allow the user to configure higher settings then those
* set by the firmware
*/
info->max_cv = info->cv;
info->max_cc = info->cc;
ret = axp288_charger_set_cv(info, cv);
if (ret < 0)
dev_warn(&info->pdev->dev,
"error(%d) in setting CV\n", ret);
}
return 0;
}
static int axp288_charger_probe(struct platform_device *pdev)
{
int ret, i, pirq;
struct axp288_chrg_info *info;
struct device *dev = &pdev->dev;
struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
struct power_supply_config charger_cfg = {};
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
......@@ -821,15 +797,8 @@ static int axp288_charger_probe(struct platform_device *pdev)
info->pdev = pdev;
info->regmap = axp20x->regmap;
info->regmap_irqc = axp20x->regmap_irqc;
info->pdata = pdev->dev.platform_data;
if (!info->pdata) {
/* Try ACPI provided pdata via device properties */
if (!device_property_present(&pdev->dev,
"axp288_charger_data\n"))
dev_err(&pdev->dev, "failed to get platform data\n");
return -ENODEV;
}
info->cable.chg_type = -1;
info->is_charger_enabled = -1;
info->cable.edev = extcon_get_extcon_dev(AXP288_EXTCON_DEV_NAME);
if (info->cable.edev == NULL) {
......@@ -838,63 +807,55 @@ static int axp288_charger_probe(struct platform_device *pdev)
return -EPROBE_DEFER;
}
/* Register for extcon notification */
INIT_WORK(&info->cable.work, axp288_charger_extcon_evt_worker);
info->cable.nb.notifier_call = axp288_charger_handle_cable_evt;
ret = extcon_register_notifier(info->cable.edev, EXTCON_CHG_USB_SDP,
&info->cable.nb);
if (ret) {
dev_err(&info->pdev->dev,
"failed to register extcon notifier for SDP %d\n", ret);
return ret;
}
ret = extcon_register_notifier(info->cable.edev, EXTCON_CHG_USB_CDP,
&info->cable.nb);
if (ret) {
dev_err(&info->pdev->dev,
"failed to register extcon notifier for CDP %d\n", ret);
extcon_unregister_notifier(info->cable.edev,
EXTCON_CHG_USB_SDP, &info->cable.nb);
return ret;
}
ret = extcon_register_notifier(info->cable.edev, EXTCON_CHG_USB_DCP,
&info->cable.nb);
if (ret) {
dev_err(&info->pdev->dev,
"failed to register extcon notifier for DCP %d\n", ret);
extcon_unregister_notifier(info->cable.edev,
EXTCON_CHG_USB_SDP, &info->cable.nb);
extcon_unregister_notifier(info->cable.edev,
EXTCON_CHG_USB_CDP, &info->cable.nb);
return ret;
info->otg.cable = extcon_get_extcon_dev(USB_HOST_EXTCON_DEV_NAME);
if (info->otg.cable == NULL) {
dev_dbg(dev, "EXTCON_USB_HOST is not ready, probe deferred\n");
return -EPROBE_DEFER;
}
platform_set_drvdata(pdev, info);
mutex_init(&info->lock);
ret = charger_init_hw_regs(info);
if (ret)
return ret;
/* Register with power supply class */
charger_cfg.drv_data = info;
info->psy_usb = power_supply_register(&pdev->dev, &axp288_charger_desc,
info->psy_usb = devm_power_supply_register(dev, &axp288_charger_desc,
&charger_cfg);
if (IS_ERR(info->psy_usb)) {
dev_err(&pdev->dev, "failed to register power supply charger\n");
ret = PTR_ERR(info->psy_usb);
goto psy_reg_failed;
dev_err(dev, "failed to register power supply: %d\n", ret);
return ret;
}
/* Register for extcon notification */
INIT_WORK(&info->cable.work, axp288_charger_extcon_evt_worker);
info->cable.nb[0].notifier_call = axp288_charger_handle_cable0_evt;
info->cable.nb[1].notifier_call = axp288_charger_handle_cable1_evt;
info->cable.nb[2].notifier_call = axp288_charger_handle_cable2_evt;
for (i = 0; i < ARRAY_SIZE(cable_ids); i++) {
ret = devm_extcon_register_notifier(dev, info->cable.edev,
cable_ids[i], &info->cable.nb[i]);
if (ret) {
dev_err(dev, "failed to register extcon notifier for %u: %d\n",
cable_ids[i], ret);
return ret;
}
}
schedule_work(&info->cable.work);
/* Register for OTG notification */
INIT_WORK(&info->otg.work, axp288_charger_otg_evt_worker);
info->otg.id_nb.notifier_call = axp288_charger_handle_otg_evt;
ret = extcon_register_notifier(info->otg.cable, EXTCON_USB_HOST,
&info->otg.id_nb);
if (ret)
dev_warn(&pdev->dev, "failed to register otg notifier\n");
if (info->otg.cable)
info->otg.id_short = extcon_get_cable_state_(
info->otg.cable, EXTCON_USB_HOST);
ret = devm_extcon_register_notifier(&pdev->dev, info->otg.cable,
EXTCON_USB_HOST, &info->otg.id_nb);
if (ret) {
dev_err(dev, "failed to register EXTCON_USB_HOST notifier\n");
return ret;
}
schedule_work(&info->otg.work);
/* Register charger interrupts */
for (i = 0; i < CHRG_INTR_END; i++) {
......@@ -903,8 +864,7 @@ static int axp288_charger_probe(struct platform_device *pdev)
if (info->irq[i] < 0) {
dev_warn(&info->pdev->dev,
"failed to get virtual interrupt=%d\n", pirq);
ret = info->irq[i];
goto intr_reg_failed;
return info->irq[i];
}
ret = devm_request_threaded_irq(&info->pdev->dev, info->irq[i],
NULL, axp288_charger_irq_thread_handler,
......@@ -912,51 +872,22 @@ static int axp288_charger_probe(struct platform_device *pdev)
if (ret) {
dev_err(&pdev->dev, "failed to request interrupt=%d\n",
info->irq[i]);
goto intr_reg_failed;
return ret;
}
}
charger_init_hw_regs(info);
return 0;
intr_reg_failed:
if (info->otg.cable)
extcon_unregister_notifier(info->otg.cable, EXTCON_USB_HOST,
&info->otg.id_nb);
power_supply_unregister(info->psy_usb);
psy_reg_failed:
extcon_unregister_notifier(info->cable.edev, EXTCON_CHG_USB_SDP,
&info->cable.nb);
extcon_unregister_notifier(info->cable.edev, EXTCON_CHG_USB_CDP,
&info->cable.nb);
extcon_unregister_notifier(info->cable.edev, EXTCON_CHG_USB_DCP,
&info->cable.nb);
return ret;
}
static int axp288_charger_remove(struct platform_device *pdev)
{
struct axp288_chrg_info *info = dev_get_drvdata(&pdev->dev);
if (info->otg.cable)
extcon_unregister_notifier(info->otg.cable, EXTCON_USB_HOST,
&info->otg.id_nb);
extcon_unregister_notifier(info->cable.edev, EXTCON_CHG_USB_SDP,
&info->cable.nb);
extcon_unregister_notifier(info->cable.edev, EXTCON_CHG_USB_CDP,
&info->cable.nb);
extcon_unregister_notifier(info->cable.edev, EXTCON_CHG_USB_DCP,
&info->cable.nb);
power_supply_unregister(info->psy_usb);
return 0;
}
static const struct platform_device_id axp288_charger_id_table[] = {
{ .name = "axp288_charger" },
{},
};
MODULE_DEVICE_TABLE(platform, axp288_charger_id_table);
static struct platform_driver axp288_charger_driver = {
.probe = axp288_charger_probe,
.remove = axp288_charger_remove,
.id_table = axp288_charger_id_table,
.driver = {
.name = "axp288_charger",
},
......
......@@ -29,6 +29,7 @@
#include <linux/iio/consumer.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <asm/unaligned.h>
#define CHRG_STAT_BAT_SAFE_MODE (1 << 3)
#define CHRG_STAT_BAT_VALID (1 << 4)
......@@ -49,23 +50,6 @@
#define CHRG_CCCV_CV_4350MV 0x3 /* 4.35V */
#define CHRG_CCCV_CHG_EN (1 << 7)
#define CV_4100 4100 /* 4100mV */
#define CV_4150 4150 /* 4150mV */
#define CV_4200 4200 /* 4200mV */
#define CV_4350 4350 /* 4350mV */
#define TEMP_IRQ_CFG_QWBTU (1 << 0)
#define TEMP_IRQ_CFG_WBTU (1 << 1)
#define TEMP_IRQ_CFG_QWBTO (1 << 2)
#define TEMP_IRQ_CFG_WBTO (1 << 3)
#define TEMP_IRQ_CFG_MASK 0xf
#define FG_IRQ_CFG_LOWBATT_WL2 (1 << 0)
#define FG_IRQ_CFG_LOWBATT_WL1 (1 << 1)
#define FG_IRQ_CFG_LOWBATT_MASK 0x3
#define LOWBAT_IRQ_STAT_LOWBATT_WL2 (1 << 0)
#define LOWBAT_IRQ_STAT_LOWBATT_WL1 (1 << 1)
#define FG_CNTL_OCV_ADJ_STAT (1 << 2)
#define FG_CNTL_OCV_ADJ_EN (1 << 3)
#define FG_CNTL_CAP_ADJ_STAT (1 << 4)
......@@ -73,17 +57,15 @@
#define FG_CNTL_CC_EN (1 << 6)
#define FG_CNTL_GAUGE_EN (1 << 7)
#define FG_15BIT_WORD_VALID (1 << 15)
#define FG_15BIT_VAL_MASK 0x7fff
#define FG_REP_CAP_VALID (1 << 7)
#define FG_REP_CAP_VAL_MASK 0x7F
#define FG_DES_CAP1_VALID (1 << 7)
#define FG_DES_CAP1_VAL_MASK 0x7F
#define FG_DES_CAP0_VAL_MASK 0xFF
#define FG_DES_CAP_RES_LSB 1456 /* 1.456mAhr */
#define FG_CC_MTR1_VALID (1 << 7)
#define FG_CC_MTR1_VAL_MASK 0x7F
#define FG_CC_MTR0_VAL_MASK 0xFF
#define FG_DES_CC_RES_LSB 1456 /* 1.456mAhr */
#define FG_OCV_CAP_VALID (1 << 7)
......@@ -104,9 +86,7 @@
/* 1.1mV per LSB expressed in uV */
#define VOLTAGE_FROM_ADC(a) ((a * 11) / 10)
/* properties converted to tenths of degrees, uV, uA, uW */
#define PROP_TEMP(a) ((a) * 10)
#define UNPROP_TEMP(a) ((a) / 10)
/* properties converted to uV, uA */
#define PROP_VOLT(a) ((a) * 1000)
#define PROP_CURR(a) ((a) * 1000)
......@@ -122,13 +102,13 @@ enum {
struct axp288_fg_info {
struct platform_device *pdev;
struct axp20x_fg_pdata *pdata;
struct regmap *regmap;
struct regmap_irq_chip_data *regmap_irqc;
int irq[AXP288_FG_INTR_NUM];
struct power_supply *bat;
struct mutex lock;
int status;
int max_volt;
struct delayed_work status_monitor;
struct dentry *debug_file;
};
......@@ -138,22 +118,14 @@ static enum power_supply_property fuel_gauge_props[] = {
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_OCV,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TEMP_MAX,
POWER_SUPPLY_PROP_TEMP_MIN,
POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_MODEL_NAME,
};
static int fuel_gauge_reg_readb(struct axp288_fg_info *info, int reg)
......@@ -169,8 +141,10 @@ static int fuel_gauge_reg_readb(struct axp288_fg_info *info, int reg)
break;
}
if (ret < 0)
if (ret < 0) {
dev_err(&info->pdev->dev, "axp288 reg read err:%d\n", ret);
return ret;
}
return val;
}
......@@ -187,6 +161,44 @@ static int fuel_gauge_reg_writeb(struct axp288_fg_info *info, int reg, u8 val)
return ret;
}
static int fuel_gauge_read_15bit_word(struct axp288_fg_info *info, int reg)
{
unsigned char buf[2];
int ret;
ret = regmap_bulk_read(info->regmap, reg, buf, 2);
if (ret < 0) {
dev_err(&info->pdev->dev, "Error reading reg 0x%02x err: %d\n",
reg, ret);
return ret;
}
ret = get_unaligned_be16(buf);
if (!(ret & FG_15BIT_WORD_VALID)) {
dev_err(&info->pdev->dev, "Error reg 0x%02x contents not valid\n",
reg);
return -ENXIO;
}
return ret & FG_15BIT_VAL_MASK;
}
static int fuel_gauge_read_12bit_word(struct axp288_fg_info *info, int reg)
{
unsigned char buf[2];
int ret;
ret = regmap_bulk_read(info->regmap, reg, buf, 2);
if (ret < 0) {
dev_err(&info->pdev->dev, "Error reading reg 0x%02x err: %d\n",
reg, ret);
return ret;
}
/* 12-bit data values have upper 8 bits in buf[0], lower 4 in buf[1] */
return (buf[0] << 4) | ((buf[1] >> 4) & 0x0f);
}
static int pmic_read_adc_val(const char *name, int *raw_val,
struct axp288_fg_info *info)
{
......@@ -247,24 +259,15 @@ static int fuel_gauge_debug_show(struct seq_file *s, void *data)
seq_printf(s, " FG_RDC0[%02x] : %02x\n",
AXP288_FG_RDC0_REG,
fuel_gauge_reg_readb(info, AXP288_FG_RDC0_REG));
seq_printf(s, " FG_OCVH[%02x] : %02x\n",
seq_printf(s, " FG_OCV[%02x] : %04x\n",
AXP288_FG_OCVH_REG,
fuel_gauge_reg_readb(info, AXP288_FG_OCVH_REG));
seq_printf(s, " FG_OCVL[%02x] : %02x\n",
AXP288_FG_OCVL_REG,
fuel_gauge_reg_readb(info, AXP288_FG_OCVL_REG));
seq_printf(s, "FG_DES_CAP1[%02x] : %02x\n",
fuel_gauge_read_12bit_word(info, AXP288_FG_OCVH_REG));
seq_printf(s, " FG_DES_CAP[%02x] : %04x\n",
AXP288_FG_DES_CAP1_REG,
fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG));
seq_printf(s, "FG_DES_CAP0[%02x] : %02x\n",
AXP288_FG_DES_CAP0_REG,
fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP0_REG));
seq_printf(s, " FG_CC_MTR1[%02x] : %02x\n",
fuel_gauge_read_15bit_word(info, AXP288_FG_DES_CAP1_REG));
seq_printf(s, " FG_CC_MTR[%02x] : %04x\n",
AXP288_FG_CC_MTR1_REG,
fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR1_REG));
seq_printf(s, " FG_CC_MTR0[%02x] : %02x\n",
AXP288_FG_CC_MTR0_REG,
fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR0_REG));
fuel_gauge_read_15bit_word(info, AXP288_FG_CC_MTR1_REG));
seq_printf(s, " FG_OCV_CAP[%02x] : %02x\n",
AXP288_FG_OCV_CAP_REG,
fuel_gauge_reg_readb(info, AXP288_FG_OCV_CAP_REG));
......@@ -417,143 +420,27 @@ static int fuel_gauge_get_current(struct axp288_fg_info *info, int *cur)
return ret;
}
static int temp_to_adc(struct axp288_fg_info *info, int tval)
{
int rntc = 0, i, ret, adc_val;
int rmin, rmax, tmin, tmax;
int tcsz = info->pdata->tcsz;
/* get the Rntc resitance value for this temp */
if (tval > info->pdata->thermistor_curve[0][1]) {
rntc = info->pdata->thermistor_curve[0][0];
} else if (tval <= info->pdata->thermistor_curve[tcsz-1][1]) {
rntc = info->pdata->thermistor_curve[tcsz-1][0];
} else {
for (i = 1; i < tcsz; i++) {
if (tval > info->pdata->thermistor_curve[i][1]) {
rmin = info->pdata->thermistor_curve[i-1][0];
rmax = info->pdata->thermistor_curve[i][0];
tmin = info->pdata->thermistor_curve[i-1][1];
tmax = info->pdata->thermistor_curve[i][1];
rntc = rmin + ((rmax - rmin) *
(tval - tmin) / (tmax - tmin));
break;
}
}
}
/* we need the current to calculate the proper adc voltage */
ret = fuel_gauge_reg_readb(info, AXP20X_ADC_RATE);
if (ret < 0) {
dev_err(&info->pdev->dev, "%s:read err:%d\n", __func__, ret);
ret = 0x30;
}
/*
* temperature is proportional to NTS thermistor resistance
* ADC_RATE[5-4] determines current, 00=20uA,01=40uA,10=60uA,11=80uA
* [12-bit ADC VAL] = R_NTC(Ω) * current / 800
*/
adc_val = rntc * (20 + (20 * ((ret >> 4) & 0x3))) / 800;
return adc_val;
}
static int adc_to_temp(struct axp288_fg_info *info, int adc_val)
{
int ret, r, i, tval = 0;
int rmin, rmax, tmin, tmax;
int tcsz = info->pdata->tcsz;
ret = fuel_gauge_reg_readb(info, AXP20X_ADC_RATE);
if (ret < 0) {
dev_err(&info->pdev->dev, "%s:read err:%d\n", __func__, ret);
ret = 0x30;
}
/*
* temperature is proportional to NTS thermistor resistance
* ADC_RATE[5-4] determines current, 00=20uA,01=40uA,10=60uA,11=80uA
* R_NTC(Ω) = [12-bit ADC VAL] * 800 / current
*/
r = adc_val * 800 / (20 + (20 * ((ret >> 4) & 0x3)));
if (r < info->pdata->thermistor_curve[0][0]) {
tval = info->pdata->thermistor_curve[0][1];
} else if (r >= info->pdata->thermistor_curve[tcsz-1][0]) {
tval = info->pdata->thermistor_curve[tcsz-1][1];
} else {
for (i = 1; i < tcsz; i++) {
if (r < info->pdata->thermistor_curve[i][0]) {
rmin = info->pdata->thermistor_curve[i-1][0];
rmax = info->pdata->thermistor_curve[i][0];
tmin = info->pdata->thermistor_curve[i-1][1];
tmax = info->pdata->thermistor_curve[i][1];
tval = tmin + ((tmax - tmin) *
(r - rmin) / (rmax - rmin));
break;
}
}
}
return tval;
}
static int fuel_gauge_get_btemp(struct axp288_fg_info *info, int *btemp)
{
int ret, raw_val = 0;
ret = pmic_read_adc_val("axp288-batt-temp", &raw_val, info);
if (ret < 0)
goto temp_read_fail;
*btemp = adc_to_temp(info, raw_val);
temp_read_fail:
return ret;
}
static int fuel_gauge_get_vocv(struct axp288_fg_info *info, int *vocv)
{
int ret, value;
/* 12-bit data value, upper 8 in OCVH, lower 4 in OCVL */
ret = fuel_gauge_reg_readb(info, AXP288_FG_OCVH_REG);
if (ret < 0)
goto vocv_read_fail;
value = ret << 4;
int ret;
ret = fuel_gauge_reg_readb(info, AXP288_FG_OCVL_REG);
if (ret < 0)
goto vocv_read_fail;
value |= (ret & 0xf);
ret = fuel_gauge_read_12bit_word(info, AXP288_FG_OCVH_REG);
if (ret >= 0)
*vocv = VOLTAGE_FROM_ADC(ret);
*vocv = VOLTAGE_FROM_ADC(value);
vocv_read_fail:
return ret;
}
static int fuel_gauge_battery_health(struct axp288_fg_info *info)
{
int temp, vocv;
int ret, health = POWER_SUPPLY_HEALTH_UNKNOWN;
ret = fuel_gauge_get_btemp(info, &temp);
if (ret < 0)
goto health_read_fail;
int ret, vocv, health = POWER_SUPPLY_HEALTH_UNKNOWN;
ret = fuel_gauge_get_vocv(info, &vocv);
if (ret < 0)
goto health_read_fail;
if (vocv > info->pdata->max_volt)
if (vocv > info->max_volt)
health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
else if (temp > info->pdata->max_temp)
health = POWER_SUPPLY_HEALTH_OVERHEAT;
else if (temp < info->pdata->min_temp)
health = POWER_SUPPLY_HEALTH_COLD;
else if (vocv < info->pdata->min_volt)
health = POWER_SUPPLY_HEALTH_DEAD;
else
health = POWER_SUPPLY_HEALTH_GOOD;
......@@ -561,28 +448,6 @@ static int fuel_gauge_battery_health(struct axp288_fg_info *info)
return health;
}
static int fuel_gauge_set_high_btemp_alert(struct axp288_fg_info *info)
{
int ret, adc_val;
/* program temperature threshold as 1/16 ADC value */
adc_val = temp_to_adc(info, info->pdata->max_temp);
ret = fuel_gauge_reg_writeb(info, AXP20X_V_HTF_DISCHRG, adc_val >> 4);
return ret;
}
static int fuel_gauge_set_low_btemp_alert(struct axp288_fg_info *info)
{
int ret, adc_val;
/* program temperature threshold as 1/16 ADC value */
adc_val = temp_to_adc(info, info->pdata->min_temp);
ret = fuel_gauge_reg_writeb(info, AXP20X_V_LTF_DISCHRG, adc_val >> 4);
return ret;
}
static int fuel_gauge_get_property(struct power_supply *ps,
enum power_supply_property prop,
union power_supply_propval *val)
......@@ -643,58 +508,25 @@ static int fuel_gauge_get_property(struct power_supply *ps,
goto fuel_gauge_read_err;
val->intval = (ret & 0x0f);
break;
case POWER_SUPPLY_PROP_TEMP:
ret = fuel_gauge_get_btemp(info, &value);
if (ret < 0)
goto fuel_gauge_read_err;
val->intval = PROP_TEMP(value);
break;
case POWER_SUPPLY_PROP_TEMP_MAX:
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
val->intval = PROP_TEMP(info->pdata->max_temp);
break;
case POWER_SUPPLY_PROP_TEMP_MIN:
case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
val->intval = PROP_TEMP(info->pdata->min_temp);
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
break;
case POWER_SUPPLY_PROP_CHARGE_NOW:
ret = fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR1_REG);
ret = fuel_gauge_read_15bit_word(info, AXP288_FG_CC_MTR1_REG);
if (ret < 0)
goto fuel_gauge_read_err;
value = (ret & FG_CC_MTR1_VAL_MASK) << 8;
ret = fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR0_REG);
if (ret < 0)
goto fuel_gauge_read_err;
value |= (ret & FG_CC_MTR0_VAL_MASK);
val->intval = value * FG_DES_CAP_RES_LSB;
val->intval = ret * FG_DES_CAP_RES_LSB;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG);
ret = fuel_gauge_read_15bit_word(info, AXP288_FG_DES_CAP1_REG);
if (ret < 0)
goto fuel_gauge_read_err;
value = (ret & FG_DES_CAP1_VAL_MASK) << 8;
ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP0_REG);
if (ret < 0)
goto fuel_gauge_read_err;
value |= (ret & FG_DES_CAP0_VAL_MASK);
val->intval = value * FG_DES_CAP_RES_LSB;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
val->intval = PROP_CURR(info->pdata->design_cap);
val->intval = ret * FG_DES_CAP_RES_LSB;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
val->intval = PROP_VOLT(info->pdata->max_volt);
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
val->intval = PROP_VOLT(info->pdata->min_volt);
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = info->pdata->battid;
val->intval = PROP_VOLT(info->max_volt);
break;
default:
mutex_unlock(&info->lock);
......@@ -718,35 +550,6 @@ static int fuel_gauge_set_property(struct power_supply *ps,
mutex_lock(&info->lock);
switch (prop) {
case POWER_SUPPLY_PROP_STATUS:
info->status = val->intval;
break;
case POWER_SUPPLY_PROP_TEMP_MIN:
case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
if ((val->intval < PD_DEF_MIN_TEMP) ||
(val->intval > PD_DEF_MAX_TEMP)) {
ret = -EINVAL;
break;
}
info->pdata->min_temp = UNPROP_TEMP(val->intval);
ret = fuel_gauge_set_low_btemp_alert(info);
if (ret < 0)
dev_err(&info->pdev->dev,
"temp alert min set fail:%d\n", ret);
break;
case POWER_SUPPLY_PROP_TEMP_MAX:
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
if ((val->intval < PD_DEF_MIN_TEMP) ||
(val->intval > PD_DEF_MAX_TEMP)) {
ret = -EINVAL;
break;
}
info->pdata->max_temp = UNPROP_TEMP(val->intval);
ret = fuel_gauge_set_high_btemp_alert(info);
if (ret < 0)
dev_err(&info->pdev->dev,
"temp alert max set fail:%d\n", ret);
break;
case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
if ((val->intval < 0) || (val->intval > 15)) {
ret = -EINVAL;
......@@ -774,11 +577,6 @@ static int fuel_gauge_property_is_writeable(struct power_supply *psy,
int ret;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
case POWER_SUPPLY_PROP_TEMP_MIN:
case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
case POWER_SUPPLY_PROP_TEMP_MAX:
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
ret = 1;
break;
......@@ -863,158 +661,6 @@ static const struct power_supply_desc fuel_gauge_desc = {
.external_power_changed = fuel_gauge_external_power_changed,
};
static int fuel_gauge_set_lowbatt_thresholds(struct axp288_fg_info *info)
{
int ret;
u8 reg_val;
ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES);
if (ret < 0) {
dev_err(&info->pdev->dev, "%s:read err:%d\n", __func__, ret);
return ret;
}
ret = (ret & FG_REP_CAP_VAL_MASK);
if (ret > FG_LOW_CAP_WARN_THR)
reg_val = FG_LOW_CAP_WARN_THR;
else if (ret > FG_LOW_CAP_CRIT_THR)
reg_val = FG_LOW_CAP_CRIT_THR;
else
reg_val = FG_LOW_CAP_SHDN_THR;
reg_val |= FG_LOW_CAP_THR1_VAL;
ret = fuel_gauge_reg_writeb(info, AXP288_FG_LOW_CAP_REG, reg_val);
if (ret < 0)
dev_err(&info->pdev->dev, "%s:write err:%d\n", __func__, ret);
return ret;
}
static int fuel_gauge_program_vbatt_full(struct axp288_fg_info *info)
{
int ret;
u8 val;
ret = fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1);
if (ret < 0)
goto fg_prog_ocv_fail;
else
val = (ret & ~CHRG_CCCV_CV_MASK);
switch (info->pdata->max_volt) {
case CV_4100:
val |= (CHRG_CCCV_CV_4100MV << CHRG_CCCV_CV_BIT_POS);
break;
case CV_4150:
val |= (CHRG_CCCV_CV_4150MV << CHRG_CCCV_CV_BIT_POS);
break;
case CV_4200:
val |= (CHRG_CCCV_CV_4200MV << CHRG_CCCV_CV_BIT_POS);
break;
case CV_4350:
val |= (CHRG_CCCV_CV_4350MV << CHRG_CCCV_CV_BIT_POS);
break;
default:
val |= (CHRG_CCCV_CV_4200MV << CHRG_CCCV_CV_BIT_POS);
break;
}
ret = fuel_gauge_reg_writeb(info, AXP20X_CHRG_CTRL1, val);
fg_prog_ocv_fail:
return ret;
}
static int fuel_gauge_program_design_cap(struct axp288_fg_info *info)
{
int ret;
ret = fuel_gauge_reg_writeb(info,
AXP288_FG_DES_CAP1_REG, info->pdata->cap1);
if (ret < 0)
goto fg_prog_descap_fail;
ret = fuel_gauge_reg_writeb(info,
AXP288_FG_DES_CAP0_REG, info->pdata->cap0);
fg_prog_descap_fail:
return ret;
}
static int fuel_gauge_program_ocv_curve(struct axp288_fg_info *info)
{
int ret = 0, i;
for (i = 0; i < OCV_CURVE_SIZE; i++) {
ret = fuel_gauge_reg_writeb(info,
AXP288_FG_OCV_CURVE_REG + i, info->pdata->ocv_curve[i]);
if (ret < 0)
goto fg_prog_ocv_fail;
}
fg_prog_ocv_fail:
return ret;
}
static int fuel_gauge_program_rdc_vals(struct axp288_fg_info *info)
{
int ret;
ret = fuel_gauge_reg_writeb(info,
AXP288_FG_RDC1_REG, info->pdata->rdc1);
if (ret < 0)
goto fg_prog_ocv_fail;
ret = fuel_gauge_reg_writeb(info,
AXP288_FG_RDC0_REG, info->pdata->rdc0);
fg_prog_ocv_fail:
return ret;
}
static void fuel_gauge_init_config_regs(struct axp288_fg_info *info)
{
int ret;
/*
* check if the config data is already
* programmed and if so just return.
*/
ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG);
if (ret < 0) {
dev_warn(&info->pdev->dev, "CAP1 reg read err!!\n");
} else if (!(ret & FG_DES_CAP1_VALID)) {
dev_info(&info->pdev->dev, "FG data needs to be initialized\n");
} else {
dev_info(&info->pdev->dev, "FG data is already initialized\n");
return;
}
ret = fuel_gauge_program_vbatt_full(info);
if (ret < 0)
dev_err(&info->pdev->dev, "set vbatt full fail:%d\n", ret);
ret = fuel_gauge_program_design_cap(info);
if (ret < 0)
dev_err(&info->pdev->dev, "set design cap fail:%d\n", ret);
ret = fuel_gauge_program_rdc_vals(info);
if (ret < 0)
dev_err(&info->pdev->dev, "set rdc fail:%d\n", ret);
ret = fuel_gauge_program_ocv_curve(info);
if (ret < 0)
dev_err(&info->pdev->dev, "set ocv curve fail:%d\n", ret);
ret = fuel_gauge_set_lowbatt_thresholds(info);
if (ret < 0)
dev_err(&info->pdev->dev, "lowbatt thr set fail:%d\n", ret);
ret = fuel_gauge_reg_writeb(info, AXP20X_CC_CTRL, 0xef);
if (ret < 0)
dev_err(&info->pdev->dev, "gauge cntl set fail:%d\n", ret);
}
static void fuel_gauge_init_irq(struct axp288_fg_info *info)
{
int ret, i, pirq;
......@@ -1052,29 +698,6 @@ static void fuel_gauge_init_irq(struct axp288_fg_info *info)
}
}
static void fuel_gauge_init_hw_regs(struct axp288_fg_info *info)
{
int ret;
unsigned int val;
ret = fuel_gauge_set_high_btemp_alert(info);
if (ret < 0)
dev_err(&info->pdev->dev, "high batt temp set fail:%d\n", ret);
ret = fuel_gauge_set_low_btemp_alert(info);
if (ret < 0)
dev_err(&info->pdev->dev, "low batt temp set fail:%d\n", ret);
/* enable interrupts */
val = fuel_gauge_reg_readb(info, AXP20X_IRQ3_EN);
val |= TEMP_IRQ_CFG_MASK;
fuel_gauge_reg_writeb(info, AXP20X_IRQ3_EN, val);
val = fuel_gauge_reg_readb(info, AXP20X_IRQ4_EN);
val |= FG_IRQ_CFG_LOWBATT_MASK;
val = fuel_gauge_reg_writeb(info, AXP20X_IRQ4_EN, val);
}
static int axp288_fuel_gauge_probe(struct platform_device *pdev)
{
int ret = 0;
......@@ -1090,15 +713,39 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev)
info->regmap = axp20x->regmap;
info->regmap_irqc = axp20x->regmap_irqc;
info->status = POWER_SUPPLY_STATUS_UNKNOWN;
info->pdata = pdev->dev.platform_data;
if (!info->pdata)
return -ENODEV;
platform_set_drvdata(pdev, info);
mutex_init(&info->lock);
INIT_DELAYED_WORK(&info->status_monitor, fuel_gauge_status_monitor);
ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG);
if (ret < 0)
return ret;
if (!(ret & FG_DES_CAP1_VALID)) {
dev_err(&pdev->dev, "axp288 not configured by firmware\n");
return -ENODEV;
}
ret = fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1);
if (ret < 0)
return ret;
switch ((ret & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS) {
case CHRG_CCCV_CV_4100MV:
info->max_volt = 4100;
break;
case CHRG_CCCV_CV_4150MV:
info->max_volt = 4150;
break;
case CHRG_CCCV_CV_4200MV:
info->max_volt = 4200;
break;
case CHRG_CCCV_CV_4350MV:
info->max_volt = 4350;
break;
}
psy_cfg.drv_data = info;
info->bat = power_supply_register(&pdev->dev, &fuel_gauge_desc, &psy_cfg);
if (IS_ERR(info->bat)) {
......@@ -1108,12 +755,10 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev)
}
fuel_gauge_create_debugfs(info);
fuel_gauge_init_config_regs(info);
fuel_gauge_init_irq(info);
fuel_gauge_init_hw_regs(info);
schedule_delayed_work(&info->status_monitor, STATUS_MON_DELAY_JIFFIES);
return ret;
return 0;
}
static const struct platform_device_id axp288_fg_id_table[] = {
......
......@@ -1569,6 +1569,11 @@ static int bq2415x_probe(struct i2c_client *client,
acpi_id =
acpi_match_device(client->dev.driver->acpi_match_table,
&client->dev);
if (!acpi_id) {
dev_err(&client->dev, "failed to match device name\n");
ret = -ENODEV;
goto error_1;
}
name = kasprintf(GFP_KERNEL, "%s-%d", acpi_id->id, num);
}
if (!name) {
......
......@@ -144,10 +144,7 @@
* so the first read after a fault returns the latched value and subsequent
* reads return the current value. In order to return the fault status
* to the user, have the interrupt handler save the reg's value and retrieve
* it in the appropriate health/status routine. Each routine has its own
* flag indicating whether it should use the value stored by the last run
* of the interrupt handler or do an actual reg read. That way each routine
* can report back whatever fault may have occured.
* it in the appropriate health/status routine.
*/
struct bq24190_dev_info {
struct i2c_client *client;
......@@ -159,10 +156,6 @@ struct bq24190_dev_info {
unsigned int gpio_int;
unsigned int irq;
struct mutex f_reg_lock;
bool first_time;
bool charger_health_valid;
bool battery_health_valid;
bool battery_status_valid;
u8 f_reg;
u8 ss_reg;
u8 watchdog;
......@@ -199,7 +192,7 @@ static const int bq24190_cvc_vreg_values[] = {
4400000
};
/* REG06[1:0] (TREG) in tenths of degrees Celcius */
/* REG06[1:0] (TREG) in tenths of degrees Celsius */
static const int bq24190_ictrc_treg_values[] = {
600, 800, 1000, 1200
};
......@@ -636,22 +629,12 @@ static int bq24190_charger_get_health(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
u8 v;
int health, ret;
int health;
mutex_lock(&bdi->f_reg_lock);
if (bdi->charger_health_valid) {
v = bdi->f_reg;
bdi->charger_health_valid = false;
mutex_unlock(&bdi->f_reg_lock);
} else {
mutex_unlock(&bdi->f_reg_lock);
ret = bq24190_read(bdi, BQ24190_REG_F, &v);
if (ret < 0)
return ret;
}
if (v & BQ24190_REG_F_BOOST_FAULT_MASK) {
/*
* This could be over-current or over-voltage but there's
......@@ -937,19 +920,9 @@ static int bq24190_battery_get_status(struct bq24190_dev_info *bdi,
int status, ret;
mutex_lock(&bdi->f_reg_lock);
if (bdi->battery_status_valid) {
chrg_fault = bdi->f_reg;
bdi->battery_status_valid = false;
mutex_unlock(&bdi->f_reg_lock);
} else {
mutex_unlock(&bdi->f_reg_lock);
ret = bq24190_read(bdi, BQ24190_REG_F, &chrg_fault);
if (ret < 0)
return ret;
}
chrg_fault &= BQ24190_REG_F_CHRG_FAULT_MASK;
chrg_fault >>= BQ24190_REG_F_CHRG_FAULT_SHIFT;
......@@ -996,21 +969,11 @@ static int bq24190_battery_get_health(struct bq24190_dev_info *bdi,
union power_supply_propval *val)
{
u8 v;
int health, ret;
int health;
mutex_lock(&bdi->f_reg_lock);
if (bdi->battery_health_valid) {
v = bdi->f_reg;
bdi->battery_health_valid = false;
mutex_unlock(&bdi->f_reg_lock);
} else {
mutex_unlock(&bdi->f_reg_lock);
ret = bq24190_read(bdi, BQ24190_REG_F, &v);
if (ret < 0)
return ret;
}
if (v & BQ24190_REG_F_BAT_FAULT_MASK) {
health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
......@@ -1197,9 +1160,12 @@ static const struct power_supply_desc bq24190_battery_desc = {
static irqreturn_t bq24190_irq_handler_thread(int irq, void *data)
{
struct bq24190_dev_info *bdi = data;
bool alert_userspace = false;
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;
bool alert_charger = false, alert_battery = false;
u8 ss_reg = 0, f_reg = 0;
int ret;
int i, ret;
pm_runtime_get_sync(bdi->dev);
......@@ -1209,6 +1175,32 @@ static irqreturn_t bq24190_irq_handler_thread(int irq, void *data)
goto out;
}
i = 0;
do {
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;
}
} while (f_reg && ++i < 2);
if (f_reg != bdi->f_reg) {
dev_info(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),
!!(f_reg & BQ24190_REG_F_BAT_FAULT_MASK),
!!(f_reg & BQ24190_REG_F_NTC_FAULT_MASK));
mutex_lock(&bdi->f_reg_lock);
if ((bdi->f_reg & battery_mask_f) != (f_reg & battery_mask_f))
alert_battery = true;
if ((bdi->f_reg & ~battery_mask_f) != (f_reg & ~battery_mask_f))
alert_charger = true;
bdi->f_reg = f_reg;
mutex_unlock(&bdi->f_reg_lock);
}
if (ss_reg != bdi->ss_reg) {
/*
* The device is in host mode so when PG_STAT goes from 1->0
......@@ -1225,47 +1217,17 @@ static irqreturn_t bq24190_irq_handler_thread(int irq, void *data)
ret);
}
if ((bdi->ss_reg & battery_mask_ss) != (ss_reg & battery_mask_ss))
alert_battery = true;
if ((bdi->ss_reg & ~battery_mask_ss) != (ss_reg & ~battery_mask_ss))
alert_charger = true;
bdi->ss_reg = ss_reg;
alert_userspace = true;
}
mutex_lock(&bdi->f_reg_lock);
ret = bq24190_read(bdi, BQ24190_REG_F, &f_reg);
if (ret < 0) {
mutex_unlock(&bdi->f_reg_lock);
dev_err(bdi->dev, "Can't read F reg: %d\n", ret);
goto out;
}
if (f_reg != bdi->f_reg) {
bdi->f_reg = f_reg;
bdi->charger_health_valid = true;
bdi->battery_health_valid = true;
bdi->battery_status_valid = true;
alert_userspace = true;
}
mutex_unlock(&bdi->f_reg_lock);
/*
* Sometimes bq24190 gives a steady trickle of interrupts even
* though the watchdog timer is turned off and neither the STATUS
* nor FAULT registers have changed. Weed out these sprurious
* interrupts so userspace isn't alerted for no reason.
* In addition, the chip always generates an interrupt after
* register reset so we should ignore that one (the very first
* interrupt received).
*/
if (alert_userspace) {
if (!bdi->first_time) {
if (alert_charger)
power_supply_changed(bdi->charger);
if (alert_battery)
power_supply_changed(bdi->battery);
} else {
bdi->first_time = false;
}
}
out:
pm_runtime_put_sync(bdi->dev);
......@@ -1300,6 +1262,10 @@ static int bq24190_hw_init(struct bq24190_dev_info *bdi)
goto out;
ret = bq24190_set_mode_host(bdi);
if (ret < 0)
goto out;
ret = bq24190_read(bdi, BQ24190_REG_SS, &bdi->ss_reg);
out:
pm_runtime_put_sync(bdi->dev);
return ret;
......@@ -1375,10 +1341,8 @@ static int bq24190_probe(struct i2c_client *client,
bdi->model = id->driver_data;
strncpy(bdi->model_name, id->name, I2C_NAME_SIZE);
mutex_init(&bdi->f_reg_lock);
bdi->first_time = true;
bdi->charger_health_valid = false;
bdi->battery_health_valid = false;
bdi->battery_status_valid = false;
bdi->f_reg = 0;
bdi->ss_reg = BQ24190_REG_SS_VBUS_STAT_MASK; /* impossible state */
i2c_set_clientdata(client, bdi);
......@@ -1392,22 +1356,13 @@ static int bq24190_probe(struct i2c_client *client,
return -EINVAL;
}
ret = devm_request_threaded_irq(dev, bdi->irq, NULL,
bq24190_irq_handler_thread,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"bq24190-charger", bdi);
if (ret < 0) {
dev_err(dev, "Can't set up irq handler\n");
goto out1;
}
pm_runtime_enable(dev);
pm_runtime_resume(dev);
ret = bq24190_hw_init(bdi);
if (ret < 0) {
dev_err(dev, "Hardware init failed\n");
goto out2;
goto out1;
}
charger_cfg.drv_data = bdi;
......@@ -1418,7 +1373,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 out2;
goto out1;
}
battery_cfg.drv_data = bdi;
......@@ -1427,27 +1382,39 @@ 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 out3;
goto out2;
}
ret = bq24190_sysfs_create_group(bdi);
if (ret) {
dev_err(dev, "Can't create sysfs entries\n");
goto out3;
}
ret = devm_request_threaded_irq(dev, bdi->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;
}
return 0;
out4:
power_supply_unregister(bdi->battery);
bq24190_sysfs_remove_group(bdi);
out3:
power_supply_unregister(bdi->charger);
power_supply_unregister(bdi->battery);
out2:
pm_runtime_disable(dev);
power_supply_unregister(bdi->charger);
out1:
pm_runtime_disable(dev);
if (bdi->gpio_int)
gpio_free(bdi->gpio_int);
return ret;
}
......@@ -1488,12 +1455,13 @@ static int bq24190_pm_resume(struct device *dev)
struct i2c_client *client = to_i2c_client(dev);
struct bq24190_dev_info *bdi = i2c_get_clientdata(client);
bdi->charger_health_valid = false;
bdi->battery_health_valid = false;
bdi->battery_status_valid = false;
bdi->f_reg = 0;
bdi->ss_reg = BQ24190_REG_SS_VBUS_STAT_MASK; /* impossible state */
pm_runtime_get_sync(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);
/* Things may have changed while suspended so alert upper layer */
......
......@@ -50,6 +50,8 @@ struct bq24735 {
struct bq24735_platform *pdata;
struct mutex lock;
struct gpio_desc *status_gpio;
struct delayed_work poll;
u32 poll_interval;
bool charging;
};
......@@ -105,26 +107,6 @@ static int bq24735_update_word(struct i2c_client *client, u8 reg,
return bq24735_write_word(client, reg, tmp);
}
static inline int bq24735_enable_charging(struct bq24735 *charger)
{
if (charger->pdata->ext_control)
return 0;
return bq24735_update_word(charger->client, BQ24735_CHG_OPT,
BQ24735_CHG_OPT_CHARGE_DISABLE,
~BQ24735_CHG_OPT_CHARGE_DISABLE);
}
static inline int bq24735_disable_charging(struct bq24735 *charger)
{
if (charger->pdata->ext_control)
return 0;
return bq24735_update_word(charger->client, BQ24735_CHG_OPT,
BQ24735_CHG_OPT_CHARGE_DISABLE,
BQ24735_CHG_OPT_CHARGE_DISABLE);
}
static int bq24735_config_charger(struct bq24735 *charger)
{
struct bq24735_platform *pdata = charger->pdata;
......@@ -176,6 +158,31 @@ static int bq24735_config_charger(struct bq24735 *charger)
return 0;
}
static inline int bq24735_enable_charging(struct bq24735 *charger)
{
int ret;
if (charger->pdata->ext_control)
return 0;
ret = bq24735_config_charger(charger);
if (ret)
return ret;
return bq24735_update_word(charger->client, BQ24735_CHG_OPT,
BQ24735_CHG_OPT_CHARGE_DISABLE, 0);
}
static inline int bq24735_disable_charging(struct bq24735 *charger)
{
if (charger->pdata->ext_control)
return 0;
return bq24735_update_word(charger->client, BQ24735_CHG_OPT,
BQ24735_CHG_OPT_CHARGE_DISABLE,
BQ24735_CHG_OPT_CHARGE_DISABLE);
}
static bool bq24735_charger_is_present(struct bq24735 *charger)
{
if (charger->status_gpio) {
......@@ -185,7 +192,7 @@ static bool bq24735_charger_is_present(struct bq24735 *charger)
ac = bq24735_read_word(charger->client, BQ24735_CHG_OPT);
if (ac < 0) {
dev_err(&charger->client->dev,
dev_dbg(&charger->client->dev,
"Failed to read charger options : %d\n",
ac);
return false;
......@@ -210,11 +217,8 @@ static int bq24735_charger_is_charging(struct bq24735 *charger)
return !(ret & BQ24735_CHG_OPT_CHARGE_DISABLE);
}
static irqreturn_t bq24735_charger_isr(int irq, void *devid)
static void bq24735_update(struct bq24735 *charger)
{
struct power_supply *psy = devid;
struct bq24735 *charger = to_bq24735(psy);
mutex_lock(&charger->lock);
if (charger->charging && bq24735_charger_is_present(charger))
......@@ -224,11 +228,29 @@ static irqreturn_t bq24735_charger_isr(int irq, void *devid)
mutex_unlock(&charger->lock);
power_supply_changed(psy);
power_supply_changed(charger->charger);
}
static irqreturn_t bq24735_charger_isr(int irq, void *devid)
{
struct power_supply *psy = devid;
struct bq24735 *charger = to_bq24735(psy);
bq24735_update(charger);
return IRQ_HANDLED;
}
static void bq24735_poll(struct work_struct *work)
{
struct bq24735 *charger = container_of(work, struct bq24735, poll.work);
bq24735_update(charger);
schedule_delayed_work(&charger->poll,
msecs_to_jiffies(charger->poll_interval));
}
static int bq24735_charger_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
......@@ -276,7 +298,6 @@ static int bq24735_charger_set_property(struct power_supply *psy,
mutex_unlock(&charger->lock);
if (ret)
return ret;
bq24735_config_charger(charger);
break;
case POWER_SUPPLY_STATUS_DISCHARGING:
case POWER_SUPPLY_STATUS_NOT_CHARGING:
......@@ -395,7 +416,7 @@ static int bq24735_charger_probe(struct i2c_client *client,
return ret;
}
if (!charger->status_gpio || bq24735_charger_is_present(charger)) {
if (bq24735_charger_is_present(charger)) {
ret = bq24735_read_word(client, BQ24735_MANUFACTURER_ID);
if (ret < 0) {
dev_err(&client->dev, "Failed to read manufacturer id : %d\n",
......@@ -416,16 +437,7 @@ static int bq24735_charger_probe(struct i2c_client *client,
"device id mismatch. 0x000b != 0x%04x\n", ret);
return -ENODEV;
}
}
ret = bq24735_config_charger(charger);
if (ret < 0) {
dev_err(&client->dev, "failed in configuring charger");
return ret;
}
/* check for AC adapter presence */
if (bq24735_charger_is_present(charger)) {
ret = bq24735_enable_charging(charger);
if (ret < 0) {
dev_err(&client->dev, "Failed to enable charging\n");
......@@ -456,11 +468,32 @@ static int bq24735_charger_probe(struct i2c_client *client,
client->irq, ret);
return ret;
}
} else {
ret = device_property_read_u32(&client->dev, "poll-interval",
&charger->poll_interval);
if (ret)
return 0;
if (!charger->poll_interval)
return 0;
INIT_DELAYED_WORK(&charger->poll, bq24735_poll);
schedule_delayed_work(&charger->poll,
msecs_to_jiffies(charger->poll_interval));
}
return 0;
}
static int bq24735_charger_remove(struct i2c_client *client)
{
struct bq24735 *charger = i2c_get_clientdata(client);
if (charger->poll_interval)
cancel_delayed_work_sync(&charger->poll);
return 0;
}
static const struct i2c_device_id bq24735_charger_id[] = {
{ "bq24735-charger", 0 },
{}
......@@ -479,6 +512,7 @@ static struct i2c_driver bq24735_charger_driver = {
.of_match_table = bq24735_match_ids,
},
.probe = bq24735_charger_probe,
.remove = bq24735_charger_remove,
.id_table = bq24735_charger_id,
};
......
......@@ -22,8 +22,14 @@
* http://www.ti.com/product/bq27010
* http://www.ti.com/product/bq27210
* http://www.ti.com/product/bq27500
* http://www.ti.com/product/bq27510-g1
* http://www.ti.com/product/bq27510-g2
* http://www.ti.com/product/bq27510-g3
* http://www.ti.com/product/bq27520-g4
* http://www.ti.com/product/bq27520-g1
* http://www.ti.com/product/bq27520-g2
* http://www.ti.com/product/bq27520-g3
* http://www.ti.com/product/bq27520-g4
* http://www.ti.com/product/bq27530-g1
* http://www.ti.com/product/bq27531-g1
* http://www.ti.com/product/bq27541-g1
......@@ -145,7 +151,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_DCAP] = 0x76,
[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
},
[BQ27500] = {
[BQ2750X] = {
[BQ27XXX_REG_CTRL] = 0x00,
[BQ27XXX_REG_TEMP] = 0x06,
[BQ27XXX_REG_INT_TEMP] = 0x28,
......@@ -164,7 +170,83 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
},
[BQ27510] = {
[BQ2751X] = {
[BQ27XXX_REG_CTRL] = 0x00,
[BQ27XXX_REG_TEMP] = 0x06,
[BQ27XXX_REG_INT_TEMP] = 0x28,
[BQ27XXX_REG_VOLT] = 0x08,
[BQ27XXX_REG_AI] = 0x14,
[BQ27XXX_REG_FLAGS] = 0x0a,
[BQ27XXX_REG_TTE] = 0x16,
[BQ27XXX_REG_TTF] = INVALID_REG_ADDR,
[BQ27XXX_REG_TTES] = 0x1a,
[BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
[BQ27XXX_REG_NAC] = 0x0c,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x1e,
[BQ27XXX_REG_AE] = INVALID_REG_ADDR,
[BQ27XXX_REG_SOC] = 0x20,
[BQ27XXX_REG_DCAP] = 0x2e,
[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
},
[BQ27500] = {
[BQ27XXX_REG_CTRL] = 0x00,
[BQ27XXX_REG_TEMP] = 0x06,
[BQ27XXX_REG_INT_TEMP] = INVALID_REG_ADDR,
[BQ27XXX_REG_VOLT] = 0x08,
[BQ27XXX_REG_AI] = 0x14,
[BQ27XXX_REG_FLAGS] = 0x0a,
[BQ27XXX_REG_TTE] = 0x16,
[BQ27XXX_REG_TTF] = 0x18,
[BQ27XXX_REG_TTES] = 0x1c,
[BQ27XXX_REG_TTECP] = 0x26,
[BQ27XXX_REG_NAC] = 0x0c,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x2a,
[BQ27XXX_REG_AE] = 0x22,
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x24,
},
[BQ27510G1] = {
[BQ27XXX_REG_CTRL] = 0x00,
[BQ27XXX_REG_TEMP] = 0x06,
[BQ27XXX_REG_INT_TEMP] = INVALID_REG_ADDR,
[BQ27XXX_REG_VOLT] = 0x08,
[BQ27XXX_REG_AI] = 0x14,
[BQ27XXX_REG_FLAGS] = 0x0a,
[BQ27XXX_REG_TTE] = 0x16,
[BQ27XXX_REG_TTF] = 0x18,
[BQ27XXX_REG_TTES] = 0x1c,
[BQ27XXX_REG_TTECP] = 0x26,
[BQ27XXX_REG_NAC] = 0x0c,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x2a,
[BQ27XXX_REG_AE] = 0x22,
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x24,
},
[BQ27510G2] = {
[BQ27XXX_REG_CTRL] = 0x00,
[BQ27XXX_REG_TEMP] = 0x06,
[BQ27XXX_REG_INT_TEMP] = INVALID_REG_ADDR,
[BQ27XXX_REG_VOLT] = 0x08,
[BQ27XXX_REG_AI] = 0x14,
[BQ27XXX_REG_FLAGS] = 0x0a,
[BQ27XXX_REG_TTE] = 0x16,
[BQ27XXX_REG_TTF] = 0x18,
[BQ27XXX_REG_TTES] = 0x1c,
[BQ27XXX_REG_TTECP] = 0x26,
[BQ27XXX_REG_NAC] = 0x0c,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x2a,
[BQ27XXX_REG_AE] = 0x22,
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x24,
},
[BQ27510G3] = {
[BQ27XXX_REG_CTRL] = 0x00,
[BQ27XXX_REG_TEMP] = 0x06,
[BQ27XXX_REG_INT_TEMP] = 0x28,
......@@ -183,6 +265,82 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
[BQ27XXX_REG_DCAP] = 0x2e,
[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
},
[BQ27520G1] = {
[BQ27XXX_REG_CTRL] = 0x00,
[BQ27XXX_REG_TEMP] = 0x06,
[BQ27XXX_REG_INT_TEMP] = INVALID_REG_ADDR,
[BQ27XXX_REG_VOLT] = 0x08,
[BQ27XXX_REG_AI] = 0x14,
[BQ27XXX_REG_FLAGS] = 0x0a,
[BQ27XXX_REG_TTE] = 0x16,
[BQ27XXX_REG_TTF] = 0x18,
[BQ27XXX_REG_TTES] = 0x1c,
[BQ27XXX_REG_TTECP] = 0x26,
[BQ27XXX_REG_NAC] = 0x0c,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = INVALID_REG_ADDR,
[BQ27XXX_REG_AE] = 0x22,
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x24,
},
[BQ27520G2] = {
[BQ27XXX_REG_CTRL] = 0x00,
[BQ27XXX_REG_TEMP] = 0x06,
[BQ27XXX_REG_INT_TEMP] = 0x36,
[BQ27XXX_REG_VOLT] = 0x08,
[BQ27XXX_REG_AI] = 0x14,
[BQ27XXX_REG_FLAGS] = 0x0a,
[BQ27XXX_REG_TTE] = 0x16,
[BQ27XXX_REG_TTF] = 0x18,
[BQ27XXX_REG_TTES] = 0x1c,
[BQ27XXX_REG_TTECP] = 0x26,
[BQ27XXX_REG_NAC] = 0x0c,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x2a,
[BQ27XXX_REG_AE] = 0x22,
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x24,
},
[BQ27520G3] = {
[BQ27XXX_REG_CTRL] = 0x00,
[BQ27XXX_REG_TEMP] = 0x06,
[BQ27XXX_REG_INT_TEMP] = 0x36,
[BQ27XXX_REG_VOLT] = 0x08,
[BQ27XXX_REG_AI] = 0x14,
[BQ27XXX_REG_FLAGS] = 0x0a,
[BQ27XXX_REG_TTE] = 0x16,
[BQ27XXX_REG_TTF] = INVALID_REG_ADDR,
[BQ27XXX_REG_TTES] = 0x1c,
[BQ27XXX_REG_TTECP] = 0x26,
[BQ27XXX_REG_NAC] = 0x0c,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x2a,
[BQ27XXX_REG_AE] = 0x22,
[BQ27XXX_REG_SOC] = 0x2c,
[BQ27XXX_REG_DCAP] = 0x3c,
[BQ27XXX_REG_AP] = 0x24,
},
[BQ27520G4] = {
[BQ27XXX_REG_CTRL] = 0x00,
[BQ27XXX_REG_TEMP] = 0x06,
[BQ27XXX_REG_INT_TEMP] = 0x28,
[BQ27XXX_REG_VOLT] = 0x08,
[BQ27XXX_REG_AI] = 0x14,
[BQ27XXX_REG_FLAGS] = 0x0a,
[BQ27XXX_REG_TTE] = 0x16,
[BQ27XXX_REG_TTF] = INVALID_REG_ADDR,
[BQ27XXX_REG_TTES] = 0x1c,
[BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
[BQ27XXX_REG_NAC] = 0x0c,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x1e,
[BQ27XXX_REG_AE] = INVALID_REG_ADDR,
[BQ27XXX_REG_SOC] = 0x20,
[BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
[BQ27XXX_REG_AP] = INVALID_REG_ADDR,
},
[BQ27530] = {
[BQ27XXX_REG_CTRL] = 0x00,
[BQ27XXX_REG_TEMP] = 0x06,
......@@ -303,7 +461,106 @@ static enum power_supply_property bq27010_battery_props[] = {
POWER_SUPPLY_PROP_MANUFACTURER,
};
static enum power_supply_property bq2750x_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_MANUFACTURER,
};
static enum power_supply_property bq2751x_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_MANUFACTURER,
};
static enum power_supply_property bq27500_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_ENERGY_NOW,
POWER_SUPPLY_PROP_POWER_AVG,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_MANUFACTURER,
};
static enum power_supply_property bq27510g1_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_ENERGY_NOW,
POWER_SUPPLY_PROP_POWER_AVG,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_MANUFACTURER,
};
static enum power_supply_property bq27510g2_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_ENERGY_NOW,
POWER_SUPPLY_PROP_POWER_AVG,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_MANUFACTURER,
};
static enum power_supply_property bq27510g3_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
......@@ -321,7 +578,27 @@ static enum power_supply_property bq27500_battery_props[] = {
POWER_SUPPLY_PROP_MANUFACTURER,
};
static enum power_supply_property bq27510_battery_props[] = {
static enum power_supply_property bq27520g1_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_ENERGY_NOW,
POWER_SUPPLY_PROP_POWER_AVG,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_MANUFACTURER,
};
static enum power_supply_property bq27520g2_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
......@@ -330,11 +607,51 @@ static enum power_supply_property bq27510_battery_props[] = {
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_ENERGY_NOW,
POWER_SUPPLY_PROP_POWER_AVG,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_MANUFACTURER,
};
static enum power_supply_property bq27520g3_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_ENERGY_NOW,
POWER_SUPPLY_PROP_POWER_AVG,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_MANUFACTURER,
};
static enum power_supply_property bq27520g4_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_MANUFACTURER,
};
......@@ -421,8 +738,16 @@ static struct {
} bq27xxx_battery_props[] = {
BQ27XXX_PROP(BQ27000, bq27000_battery_props),
BQ27XXX_PROP(BQ27010, bq27010_battery_props),
BQ27XXX_PROP(BQ2750X, bq2750x_battery_props),
BQ27XXX_PROP(BQ2751X, bq2751x_battery_props),
BQ27XXX_PROP(BQ27500, bq27500_battery_props),
BQ27XXX_PROP(BQ27510, bq27510_battery_props),
BQ27XXX_PROP(BQ27510G1, bq27510g1_battery_props),
BQ27XXX_PROP(BQ27510G2, bq27510g2_battery_props),
BQ27XXX_PROP(BQ27510G3, bq27510g3_battery_props),
BQ27XXX_PROP(BQ27520G1, bq27520g1_battery_props),
BQ27XXX_PROP(BQ27520G2, bq27520g2_battery_props),
BQ27XXX_PROP(BQ27520G3, bq27520g3_battery_props),
BQ27XXX_PROP(BQ27520G4, bq27520g4_battery_props),
BQ27XXX_PROP(BQ27530, bq27530_battery_props),
BQ27XXX_PROP(BQ27541, bq27541_battery_props),
BQ27XXX_PROP(BQ27545, bq27545_battery_props),
......@@ -674,13 +999,26 @@ static int bq27xxx_battery_read_pwr_avg(struct bq27xxx_device_info *di)
*/
static bool bq27xxx_battery_overtemp(struct bq27xxx_device_info *di, u16 flags)
{
if (di->chip == BQ27500 || di->chip == BQ27510 ||
di->chip == BQ27541 || di->chip == BQ27545)
switch (di->chip) {
case BQ2750X:
case BQ2751X:
case BQ27500:
case BQ27510G1:
case BQ27510G2:
case BQ27510G3:
case BQ27520G1:
case BQ27520G2:
case BQ27520G3:
case BQ27520G4:
case BQ27541:
case BQ27545:
return flags & (BQ27XXX_FLAG_OTC | BQ27XXX_FLAG_OTD);
if (di->chip == BQ27530 || di->chip == BQ27421)
case BQ27530:
case BQ27421:
return flags & BQ27XXX_FLAG_OT;
default:
return false;
}
}
/*
......
......@@ -148,9 +148,17 @@ static int bq27xxx_battery_i2c_remove(struct i2c_client *client)
static const struct i2c_device_id bq27xxx_i2c_id_table[] = {
{ "bq27200", BQ27000 },
{ "bq27210", BQ27010 },
{ "bq27500", BQ27500 },
{ "bq27510", BQ27510 },
{ "bq27520", BQ27510 },
{ "bq27500", BQ2750X },
{ "bq27510", BQ2751X },
{ "bq27520", BQ2751X },
{ "bq27500-1", BQ27500 },
{ "bq27510g1", BQ27510G1 },
{ "bq27510g2", BQ27510G2 },
{ "bq27510g3", BQ27510G3 },
{ "bq27520g1", BQ27520G1 },
{ "bq27520g2", BQ27520G2 },
{ "bq27520g3", BQ27520G3 },
{ "bq27520g4", BQ27520G4 },
{ "bq27530", BQ27530 },
{ "bq27531", BQ27530 },
{ "bq27541", BQ27541 },
......@@ -173,6 +181,14 @@ static const struct of_device_id bq27xxx_battery_i2c_of_match_table[] = {
{ .compatible = "ti,bq27500" },
{ .compatible = "ti,bq27510" },
{ .compatible = "ti,bq27520" },
{ .compatible = "ti,bq27500-1" },
{ .compatible = "ti,bq27510g1" },
{ .compatible = "ti,bq27510g2" },
{ .compatible = "ti,bq27510g3" },
{ .compatible = "ti,bq27520g1" },
{ .compatible = "ti,bq27520g2" },
{ .compatible = "ti,bq27520g3" },
{ .compatible = "ti,bq27520g4" },
{ .compatible = "ti,bq27530" },
{ .compatible = "ti,bq27531" },
{ .compatible = "ti,bq27541" },
......
......@@ -14,7 +14,7 @@
*/
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/gpio.h> /* For legacy platform data */
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
......@@ -23,7 +23,7 @@
#include <linux/power_supply.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/power/gpio-charger.h>
......@@ -34,6 +34,8 @@ struct gpio_charger {
struct power_supply *charger;
struct power_supply_desc charger_desc;
struct gpio_desc *gpiod;
bool legacy_gpio_requested;
};
static irqreturn_t gpio_charger_irq(int irq, void *devid)
......@@ -58,7 +60,8 @@ static int gpio_charger_get_property(struct power_supply *psy,
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
val->intval = !!gpio_get_value_cansleep(pdata->gpio);
val->intval = gpiod_get_value_cansleep(gpio_charger->gpiod);
/* This xor is only ever used with legacy pdata GPIO */
val->intval ^= pdata->gpio_active_low;
break;
default:
......@@ -78,7 +81,6 @@ struct gpio_charger_platform_data *gpio_charger_parse_dt(struct device *dev)
struct device_node *np = dev->of_node;
struct gpio_charger_platform_data *pdata;
const char *chargetype;
enum of_gpio_flags flags;
int ret;
if (!np)
......@@ -89,16 +91,6 @@ struct gpio_charger_platform_data *gpio_charger_parse_dt(struct device *dev)
return ERR_PTR(-ENOMEM);
pdata->name = np->name;
pdata->gpio = of_get_gpio_flags(np, 0, &flags);
if (pdata->gpio < 0) {
if (pdata->gpio != -EPROBE_DEFER)
dev_err(dev, "could not get charger gpio\n");
return ERR_PTR(pdata->gpio);
}
pdata->gpio_active_low = !!(flags & OF_GPIO_ACTIVE_LOW);
pdata->type = POWER_SUPPLY_TYPE_UNKNOWN;
ret = of_property_read_string(np, "charger-type", &chargetype);
if (ret >= 0) {
......@@ -144,11 +136,6 @@ static int gpio_charger_probe(struct platform_device *pdev)
}
}
if (!gpio_is_valid(pdata->gpio)) {
dev_err(&pdev->dev, "Invalid gpio pin\n");
return -EINVAL;
}
gpio_charger = devm_kzalloc(&pdev->dev, sizeof(*gpio_charger),
GFP_KERNEL);
if (!gpio_charger) {
......@@ -156,6 +143,45 @@ static int gpio_charger_probe(struct platform_device *pdev)
return -ENOMEM;
}
/*
* This will fetch a GPIO descriptor from device tree, ACPI or
* boardfile descriptor tables. It's good to try this first.
*/
gpio_charger->gpiod = devm_gpiod_get(&pdev->dev, NULL, GPIOD_IN);
/*
* If this fails and we're not using device tree, try the
* legacy platform data method.
*/
if (IS_ERR(gpio_charger->gpiod) && !pdev->dev.of_node) {
/* Non-DT: use legacy GPIO numbers */
if (!gpio_is_valid(pdata->gpio)) {
dev_err(&pdev->dev, "Invalid gpio pin in pdata\n");
return -EINVAL;
}
ret = gpio_request(pdata->gpio, dev_name(&pdev->dev));
if (ret) {
dev_err(&pdev->dev, "Failed to request gpio pin: %d\n",
ret);
return ret;
}
gpio_charger->legacy_gpio_requested = true;
ret = gpio_direction_input(pdata->gpio);
if (ret) {
dev_err(&pdev->dev, "Failed to set gpio to input: %d\n",
ret);
goto err_gpio_free;
}
/* Then convert this to gpiod for now */
gpio_charger->gpiod = gpio_to_desc(pdata->gpio);
} else if (IS_ERR(gpio_charger->gpiod)) {
/* Just try again if this happens */
if (PTR_ERR(gpio_charger->gpiod) == -EPROBE_DEFER)
return -EPROBE_DEFER;
dev_err(&pdev->dev, "error getting GPIO descriptor\n");
return PTR_ERR(gpio_charger->gpiod);
}
charger_desc = &gpio_charger->charger_desc;
charger_desc->name = pdata->name ? pdata->name : "gpio-charger";
......@@ -169,17 +195,6 @@ static int gpio_charger_probe(struct platform_device *pdev)
psy_cfg.of_node = pdev->dev.of_node;
psy_cfg.drv_data = gpio_charger;
ret = gpio_request(pdata->gpio, dev_name(&pdev->dev));
if (ret) {
dev_err(&pdev->dev, "Failed to request gpio pin: %d\n", ret);
goto err_free;
}
ret = gpio_direction_input(pdata->gpio);
if (ret) {
dev_err(&pdev->dev, "Failed to set gpio to input: %d\n", ret);
goto err_gpio_free;
}
gpio_charger->pdata = pdata;
gpio_charger->charger = power_supply_register(&pdev->dev,
......@@ -191,7 +206,7 @@ static int gpio_charger_probe(struct platform_device *pdev)
goto err_gpio_free;
}
irq = gpio_to_irq(pdata->gpio);
irq = gpiod_to_irq(gpio_charger->gpiod);
if (irq > 0) {
ret = request_any_context_irq(irq, gpio_charger_irq,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
......@@ -209,8 +224,8 @@ static int gpio_charger_probe(struct platform_device *pdev)
return 0;
err_gpio_free:
if (gpio_charger->legacy_gpio_requested)
gpio_free(pdata->gpio);
err_free:
return ret;
}
......@@ -223,6 +238,7 @@ static int gpio_charger_remove(struct platform_device *pdev)
power_supply_unregister(gpio_charger->charger);
if (gpio_charger->legacy_gpio_requested)
gpio_free(gpio_charger->pdata->gpio);
return 0;
......
/*
* intel_mid_battery.c - Intel MID PMIC Battery Driver
*
* Copyright (C) 2009 Intel Corporation
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Author: Nithish Mahalingam <nithish.mahalingam@intel.com>
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/jiffies.h>
#include <linux/param.h>
#include <linux/device.h>
#include <linux/spi/spi.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <asm/intel_scu_ipc.h>
#define DRIVER_NAME "pmic_battery"
/*********************************************************************
* Generic defines
*********************************************************************/
static int debug;
module_param(debug, int, 0444);
MODULE_PARM_DESC(debug, "Flag to enable PMIC Battery debug messages.");
#define PMIC_BATT_DRV_INFO_UPDATED 1
#define PMIC_BATT_PRESENT 1
#define PMIC_BATT_NOT_PRESENT 0
#define PMIC_USB_PRESENT PMIC_BATT_PRESENT
#define PMIC_USB_NOT_PRESENT PMIC_BATT_NOT_PRESENT
/* pmic battery register related */
#define PMIC_BATT_CHR_SCHRGINT_ADDR 0xD2
#define PMIC_BATT_CHR_SBATOVP_MASK (1 << 1)
#define PMIC_BATT_CHR_STEMP_MASK (1 << 2)
#define PMIC_BATT_CHR_SCOMP_MASK (1 << 3)
#define PMIC_BATT_CHR_SUSBDET_MASK (1 << 4)
#define PMIC_BATT_CHR_SBATDET_MASK (1 << 5)
#define PMIC_BATT_CHR_SDCLMT_MASK (1 << 6)
#define PMIC_BATT_CHR_SUSBOVP_MASK (1 << 7)
#define PMIC_BATT_CHR_EXCPT_MASK 0x86
#define PMIC_BATT_ADC_ACCCHRG_MASK (1 << 31)
#define PMIC_BATT_ADC_ACCCHRGVAL_MASK 0x7FFFFFFF
/* pmic ipc related */
#define PMIC_BATT_CHR_IPC_FCHRG_SUBID 0x4
#define PMIC_BATT_CHR_IPC_TCHRG_SUBID 0x6
/* types of battery charging */
enum batt_charge_type {
BATT_USBOTG_500MA_CHARGE,
BATT_USBOTG_TRICKLE_CHARGE,
};
/* valid battery events */
enum batt_event {
BATT_EVENT_BATOVP_EXCPT,
BATT_EVENT_USBOVP_EXCPT,
BATT_EVENT_TEMP_EXCPT,
BATT_EVENT_DCLMT_EXCPT,
BATT_EVENT_EXCPT
};
/*********************************************************************
* Battery properties
*********************************************************************/
/*
* pmic battery info
*/
struct pmic_power_module_info {
bool is_dev_info_updated;
struct device *dev;
/* pmic battery data */
unsigned long update_time; /* jiffies when data read */
unsigned int usb_is_present;
unsigned int batt_is_present;
unsigned int batt_health;
unsigned int usb_health;
unsigned int batt_status;
unsigned int batt_charge_now; /* in mAS */
unsigned int batt_prev_charge_full; /* in mAS */
unsigned int batt_charge_rate; /* in units per second */
struct power_supply *usb;
struct power_supply *batt;
int irq; /* GPE_ID or IRQ# */
struct workqueue_struct *monitor_wqueue;
struct delayed_work monitor_battery;
struct work_struct handler;
};
static unsigned int delay_time = 2000; /* in ms */
/*
* pmic ac properties
*/
static enum power_supply_property pmic_usb_props[] = {
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_HEALTH,
};
/*
* pmic battery properties
*/
static enum power_supply_property pmic_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL,
};
/*
* Glue functions for talking to the IPC
*/
struct battery_property {
u32 capacity; /* Charger capacity */
u8 crnt; /* Quick charge current value*/
u8 volt; /* Fine adjustment of constant charge voltage */
u8 prot; /* CHRGPROT register value */
u8 prot2; /* CHRGPROT1 register value */
u8 timer; /* Charging timer */
};
#define IPCMSG_BATTERY 0xEF
/* Battery coulomb counter accumulator commands */
#define IPC_CMD_CC_WR 0 /* Update coulomb counter value */
#define IPC_CMD_CC_RD 1 /* Read coulomb counter value */
#define IPC_CMD_BATTERY_PROPERTY 2 /* Read Battery property */
/**
* pmic_scu_ipc_battery_cc_read - read battery cc
* @value: battery coulomb counter read
*
* Reads the battery couloumb counter value, returns 0 on success, or
* an error code
*
* This function may sleep. Locking for SCU accesses is handled for
* the caller.
*/
static int pmic_scu_ipc_battery_cc_read(u32 *value)
{
return intel_scu_ipc_command(IPCMSG_BATTERY, IPC_CMD_CC_RD,
NULL, 0, value, 1);
}
/**
* pmic_scu_ipc_battery_property_get - fetch properties
* @prop: battery properties
*
* Retrieve the battery properties from the power management
*
* This function may sleep. Locking for SCU accesses is handled for
* the caller.
*/
static int pmic_scu_ipc_battery_property_get(struct battery_property *prop)
{
u32 data[3];
u8 *p = (u8 *)&data[1];
int err = intel_scu_ipc_command(IPCMSG_BATTERY,
IPC_CMD_BATTERY_PROPERTY, NULL, 0, data, 3);
prop->capacity = data[0];
prop->crnt = *p++;
prop->volt = *p++;
prop->prot = *p++;
prop->prot2 = *p++;
prop->timer = *p++;
return err;
}
/**
* pmic_scu_ipc_set_charger - set charger
* @charger: charger to select
*
* Switch the charging mode for the SCU
*/
static int pmic_scu_ipc_set_charger(int charger)
{
return intel_scu_ipc_simple_command(IPCMSG_BATTERY, charger);
}
/**
* pmic_battery_log_event - log battery events
* @event: battery event to be logged
* Context: can sleep
*
* There are multiple battery events which may be of interest to users;
* this battery function logs the different battery events onto the
* kernel log messages.
*/
static void pmic_battery_log_event(enum batt_event event)
{
printk(KERN_WARNING "pmic-battery: ");
switch (event) {
case BATT_EVENT_BATOVP_EXCPT:
printk(KERN_CONT "battery overvoltage condition\n");
break;
case BATT_EVENT_USBOVP_EXCPT:
printk(KERN_CONT "usb charger overvoltage condition\n");
break;
case BATT_EVENT_TEMP_EXCPT:
printk(KERN_CONT "high battery temperature condition\n");
break;
case BATT_EVENT_DCLMT_EXCPT:
printk(KERN_CONT "over battery charge current condition\n");
break;
default:
printk(KERN_CONT "charger/battery exception %d\n", event);
break;
}
}
/**
* pmic_battery_read_status - read battery status information
* @pbi: device info structure to update the read information
* Context: can sleep
*
* PMIC power source information need to be updated based on the data read
* from the PMIC battery registers.
*
*/
static void pmic_battery_read_status(struct pmic_power_module_info *pbi)
{
unsigned int update_time_intrvl;
unsigned int chrg_val;
u32 ccval;
u8 r8;
struct battery_property batt_prop;
int batt_present = 0;
int usb_present = 0;
int batt_exception = 0;
/* make sure the last batt_status read happened delay_time before */
if (pbi->update_time && time_before(jiffies, pbi->update_time +
msecs_to_jiffies(delay_time)))
return;
update_time_intrvl = jiffies_to_msecs(jiffies - pbi->update_time);
pbi->update_time = jiffies;
/* read coulomb counter registers and schrgint register */
if (pmic_scu_ipc_battery_cc_read(&ccval)) {
dev_warn(pbi->dev, "%s(): ipc config cmd failed\n",
__func__);
return;
}
if (intel_scu_ipc_ioread8(PMIC_BATT_CHR_SCHRGINT_ADDR, &r8)) {
dev_warn(pbi->dev, "%s(): ipc pmic read failed\n",
__func__);
return;
}
/*
* set pmic_power_module_info members based on pmic register values
* read.
*/
/* set batt_is_present */
if (r8 & PMIC_BATT_CHR_SBATDET_MASK) {
pbi->batt_is_present = PMIC_BATT_PRESENT;
batt_present = 1;
} else {
pbi->batt_is_present = PMIC_BATT_NOT_PRESENT;
pbi->batt_health = POWER_SUPPLY_HEALTH_UNKNOWN;
pbi->batt_status = POWER_SUPPLY_STATUS_UNKNOWN;
}
/* set batt_health */
if (batt_present) {
if (r8 & PMIC_BATT_CHR_SBATOVP_MASK) {
pbi->batt_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
pmic_battery_log_event(BATT_EVENT_BATOVP_EXCPT);
batt_exception = 1;
} else if (r8 & PMIC_BATT_CHR_STEMP_MASK) {
pbi->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT;
pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
pmic_battery_log_event(BATT_EVENT_TEMP_EXCPT);
batt_exception = 1;
} else {
pbi->batt_health = POWER_SUPPLY_HEALTH_GOOD;
if (r8 & PMIC_BATT_CHR_SDCLMT_MASK) {
/* PMIC will change charging current automatically */
pmic_battery_log_event(BATT_EVENT_DCLMT_EXCPT);
}
}
}
/* set usb_is_present */
if (r8 & PMIC_BATT_CHR_SUSBDET_MASK) {
pbi->usb_is_present = PMIC_USB_PRESENT;
usb_present = 1;
} else {
pbi->usb_is_present = PMIC_USB_NOT_PRESENT;
pbi->usb_health = POWER_SUPPLY_HEALTH_UNKNOWN;
}
if (usb_present) {
if (r8 & PMIC_BATT_CHR_SUSBOVP_MASK) {
pbi->usb_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
pmic_battery_log_event(BATT_EVENT_USBOVP_EXCPT);
} else {
pbi->usb_health = POWER_SUPPLY_HEALTH_GOOD;
}
}
chrg_val = ccval & PMIC_BATT_ADC_ACCCHRGVAL_MASK;
/* set batt_prev_charge_full to battery capacity the first time */
if (!pbi->is_dev_info_updated) {
if (pmic_scu_ipc_battery_property_get(&batt_prop)) {
dev_warn(pbi->dev, "%s(): ipc config cmd failed\n",
__func__);
return;
}
pbi->batt_prev_charge_full = batt_prop.capacity;
}
/* set batt_status */
if (batt_present && !batt_exception) {
if (r8 & PMIC_BATT_CHR_SCOMP_MASK) {
pbi->batt_status = POWER_SUPPLY_STATUS_FULL;
pbi->batt_prev_charge_full = chrg_val;
} else if (ccval & PMIC_BATT_ADC_ACCCHRG_MASK) {
pbi->batt_status = POWER_SUPPLY_STATUS_DISCHARGING;
} else {
pbi->batt_status = POWER_SUPPLY_STATUS_CHARGING;
}
}
/* set batt_charge_rate */
if (pbi->is_dev_info_updated && batt_present && !batt_exception) {
if (pbi->batt_status == POWER_SUPPLY_STATUS_DISCHARGING) {
if (pbi->batt_charge_now - chrg_val) {
pbi->batt_charge_rate = ((pbi->batt_charge_now -
chrg_val) * 1000 * 60) /
update_time_intrvl;
}
} else if (pbi->batt_status == POWER_SUPPLY_STATUS_CHARGING) {
if (chrg_val - pbi->batt_charge_now) {
pbi->batt_charge_rate = ((chrg_val -
pbi->batt_charge_now) * 1000 * 60) /
update_time_intrvl;
}
} else
pbi->batt_charge_rate = 0;
} else {
pbi->batt_charge_rate = -1;
}
/* batt_charge_now */
if (batt_present && !batt_exception)
pbi->batt_charge_now = chrg_val;
else
pbi->batt_charge_now = -1;
pbi->is_dev_info_updated = PMIC_BATT_DRV_INFO_UPDATED;
}
/**
* pmic_usb_get_property - usb power source get property
* @psy: usb power supply context
* @psp: usb power source property
* @val: usb power source property value
* Context: can sleep
*
* PMIC usb power source property needs to be provided to power_supply
* subsytem for it to provide the information to users.
*/
static int pmic_usb_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct pmic_power_module_info *pbi = power_supply_get_drvdata(psy);
/* update pmic_power_module_info members */
pmic_battery_read_status(pbi);
switch (psp) {
case POWER_SUPPLY_PROP_PRESENT:
val->intval = pbi->usb_is_present;
break;
case POWER_SUPPLY_PROP_HEALTH:
val->intval = pbi->usb_health;
break;
default:
return -EINVAL;
}
return 0;
}
static inline unsigned long mAStouAh(unsigned long v)
{
/* seconds to hours, mA to µA */
return (v * 1000) / 3600;
}
/**
* pmic_battery_get_property - battery power source get property
* @psy: battery power supply context
* @psp: battery power source property
* @val: battery power source property value
* Context: can sleep
*
* PMIC battery power source property needs to be provided to power_supply
* subsytem for it to provide the information to users.
*/
static int pmic_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct pmic_power_module_info *pbi = power_supply_get_drvdata(psy);
/* update pmic_power_module_info members */
pmic_battery_read_status(pbi);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = pbi->batt_status;
break;
case POWER_SUPPLY_PROP_HEALTH:
val->intval = pbi->batt_health;
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = pbi->batt_is_present;
break;
case POWER_SUPPLY_PROP_CHARGE_NOW:
val->intval = mAStouAh(pbi->batt_charge_now);
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
val->intval = mAStouAh(pbi->batt_prev_charge_full);
break;
default:
return -EINVAL;
}
return 0;
}
/**
* pmic_battery_monitor - monitor battery status
* @work: work structure
* Context: can sleep
*
* PMIC battery status needs to be monitored for any change
* and information needs to be frequently updated.
*/
static void pmic_battery_monitor(struct work_struct *work)
{
struct pmic_power_module_info *pbi = container_of(work,
struct pmic_power_module_info, monitor_battery.work);
/* update pmic_power_module_info members */
pmic_battery_read_status(pbi);
queue_delayed_work(pbi->monitor_wqueue, &pbi->monitor_battery, HZ * 10);
}
/**
* pmic_battery_set_charger - set battery charger
* @pbi: device info structure
* @chrg: charge mode to set battery charger in
* Context: can sleep
*
* PMIC battery charger needs to be enabled based on the usb charge
* capabilities connected to the platform.
*/
static int pmic_battery_set_charger(struct pmic_power_module_info *pbi,
enum batt_charge_type chrg)
{
int retval;
/* set usblmt bits and chrgcntl register bits appropriately */
switch (chrg) {
case BATT_USBOTG_500MA_CHARGE:
retval = pmic_scu_ipc_set_charger(PMIC_BATT_CHR_IPC_FCHRG_SUBID);
break;
case BATT_USBOTG_TRICKLE_CHARGE:
retval = pmic_scu_ipc_set_charger(PMIC_BATT_CHR_IPC_TCHRG_SUBID);
break;
default:
dev_warn(pbi->dev, "%s(): out of range usb charger "
"charge detected\n", __func__);
return -EINVAL;
}
if (retval) {
dev_warn(pbi->dev, "%s(): ipc pmic read failed\n",
__func__);
return retval;
}
return 0;
}
/**
* pmic_battery_interrupt_handler - pmic battery interrupt handler
* Context: interrupt context
*
* PMIC battery interrupt handler which will be called with either
* battery full condition occurs or usb otg & battery connect
* condition occurs.
*/
static irqreturn_t pmic_battery_interrupt_handler(int id, void *dev)
{
struct pmic_power_module_info *pbi = dev;
schedule_work(&pbi->handler);
return IRQ_HANDLED;
}
/**
* pmic_battery_handle_intrpt - pmic battery service interrupt
* @work: work structure
* Context: can sleep
*
* PMIC battery needs to either update the battery status as full
* if it detects battery full condition caused the interrupt or needs
* to enable battery charger if it detects usb and battery detect
* caused the source of interrupt.
*/
static void pmic_battery_handle_intrpt(struct work_struct *work)
{
struct pmic_power_module_info *pbi = container_of(work,
struct pmic_power_module_info, handler);
enum batt_charge_type chrg;
u8 r8;
if (intel_scu_ipc_ioread8(PMIC_BATT_CHR_SCHRGINT_ADDR, &r8)) {
dev_warn(pbi->dev, "%s(): ipc pmic read failed\n",
__func__);
return;
}
/* find the cause of the interrupt */
if (r8 & PMIC_BATT_CHR_SBATDET_MASK) {
pbi->batt_is_present = PMIC_BATT_PRESENT;
} else {
pbi->batt_is_present = PMIC_BATT_NOT_PRESENT;
pbi->batt_health = POWER_SUPPLY_HEALTH_UNKNOWN;
pbi->batt_status = POWER_SUPPLY_STATUS_UNKNOWN;
return;
}
if (r8 & PMIC_BATT_CHR_EXCPT_MASK) {
pbi->batt_health = POWER_SUPPLY_HEALTH_UNKNOWN;
pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
pbi->usb_health = POWER_SUPPLY_HEALTH_UNKNOWN;
pmic_battery_log_event(BATT_EVENT_EXCPT);
return;
} else {
pbi->batt_health = POWER_SUPPLY_HEALTH_GOOD;
pbi->usb_health = POWER_SUPPLY_HEALTH_GOOD;
}
if (r8 & PMIC_BATT_CHR_SCOMP_MASK) {
u32 ccval;
pbi->batt_status = POWER_SUPPLY_STATUS_FULL;
if (pmic_scu_ipc_battery_cc_read(&ccval)) {
dev_warn(pbi->dev, "%s(): ipc config cmd "
"failed\n", __func__);
return;
}
pbi->batt_prev_charge_full = ccval &
PMIC_BATT_ADC_ACCCHRGVAL_MASK;
return;
}
if (r8 & PMIC_BATT_CHR_SUSBDET_MASK) {
pbi->usb_is_present = PMIC_USB_PRESENT;
} else {
pbi->usb_is_present = PMIC_USB_NOT_PRESENT;
pbi->usb_health = POWER_SUPPLY_HEALTH_UNKNOWN;
return;
}
/* setup battery charging */
#if 0
/* check usb otg power capability and set charger accordingly */
retval = langwell_udc_maxpower(&power);
if (retval) {
dev_warn(pbi->dev,
"%s(): usb otg power query failed with error code %d\n",
__func__, retval);
return;
}
if (power >= 500)
chrg = BATT_USBOTG_500MA_CHARGE;
else
#endif
chrg = BATT_USBOTG_TRICKLE_CHARGE;
/* enable battery charging */
if (pmic_battery_set_charger(pbi, chrg)) {
dev_warn(pbi->dev,
"%s(): failed to set up battery charging\n", __func__);
return;
}
dev_dbg(pbi->dev,
"pmic-battery: %s() - setting up battery charger successful\n",
__func__);
}
/*
* Description of power supplies
*/
static const struct power_supply_desc pmic_usb_desc = {
.name = "pmic-usb",
.type = POWER_SUPPLY_TYPE_USB,
.properties = pmic_usb_props,
.num_properties = ARRAY_SIZE(pmic_usb_props),
.get_property = pmic_usb_get_property,
};
static const struct power_supply_desc pmic_batt_desc = {
.name = "pmic-batt",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = pmic_battery_props,
.num_properties = ARRAY_SIZE(pmic_battery_props),
.get_property = pmic_battery_get_property,
};
/**
* pmic_battery_probe - pmic battery initialize
* @irq: pmic battery device irq
* @dev: pmic battery device structure
* Context: can sleep
*
* PMIC battery initializes its internal data structue and other
* infrastructure components for it to work as expected.
*/
static int probe(int irq, struct device *dev)
{
int retval = 0;
struct pmic_power_module_info *pbi;
struct power_supply_config psy_cfg = {};
dev_dbg(dev, "pmic-battery: found pmic battery device\n");
pbi = kzalloc(sizeof(*pbi), GFP_KERNEL);
if (!pbi) {
dev_err(dev, "%s(): memory allocation failed\n",
__func__);
return -ENOMEM;
}
pbi->dev = dev;
pbi->irq = irq;
dev_set_drvdata(dev, pbi);
psy_cfg.drv_data = pbi;
/* initialize all required framework before enabling interrupts */
INIT_WORK(&pbi->handler, pmic_battery_handle_intrpt);
INIT_DELAYED_WORK(&pbi->monitor_battery, pmic_battery_monitor);
pbi->monitor_wqueue = alloc_workqueue(dev_name(dev), WQ_MEM_RECLAIM, 0);
if (!pbi->monitor_wqueue) {
dev_err(dev, "%s(): wqueue init failed\n", __func__);
retval = -ESRCH;
goto wqueue_failed;
}
/* register interrupt */
retval = request_irq(pbi->irq, pmic_battery_interrupt_handler,
0, DRIVER_NAME, pbi);
if (retval) {
dev_err(dev, "%s(): cannot get IRQ\n", __func__);
goto requestirq_failed;
}
/* register pmic-batt with power supply subsystem */
pbi->batt = power_supply_register(dev, &pmic_usb_desc, &psy_cfg);
if (IS_ERR(pbi->batt)) {
dev_err(dev,
"%s(): failed to register pmic battery device with power supply subsystem\n",
__func__);
retval = PTR_ERR(pbi->batt);
goto power_reg_failed;
}
dev_dbg(dev, "pmic-battery: %s() - pmic battery device "
"registration with power supply subsystem successful\n",
__func__);
queue_delayed_work(pbi->monitor_wqueue, &pbi->monitor_battery, HZ * 1);
/* register pmic-usb with power supply subsystem */
pbi->usb = power_supply_register(dev, &pmic_batt_desc, &psy_cfg);
if (IS_ERR(pbi->usb)) {
dev_err(dev,
"%s(): failed to register pmic usb device with power supply subsystem\n",
__func__);
retval = PTR_ERR(pbi->usb);
goto power_reg_failed_1;
}
if (debug)
printk(KERN_INFO "pmic-battery: %s() - pmic usb device "
"registration with power supply subsystem successful\n",
__func__);
return retval;
power_reg_failed_1:
power_supply_unregister(pbi->batt);
power_reg_failed:
cancel_delayed_work_sync(&pbi->monitor_battery);
requestirq_failed:
destroy_workqueue(pbi->monitor_wqueue);
wqueue_failed:
kfree(pbi);
return retval;
}
static int platform_pmic_battery_probe(struct platform_device *pdev)
{
return probe(pdev->id, &pdev->dev);
}
/**
* pmic_battery_remove - pmic battery finalize
* @dev: pmic battery device structure
* Context: can sleep
*
* PMIC battery finalizes its internal data structue and other
* infrastructure components that it initialized in
* pmic_battery_probe.
*/
static int platform_pmic_battery_remove(struct platform_device *pdev)
{
struct pmic_power_module_info *pbi = platform_get_drvdata(pdev);
free_irq(pbi->irq, pbi);
cancel_delayed_work_sync(&pbi->monitor_battery);
destroy_workqueue(pbi->monitor_wqueue);
power_supply_unregister(pbi->usb);
power_supply_unregister(pbi->batt);
cancel_work_sync(&pbi->handler);
kfree(pbi);
return 0;
}
static struct platform_driver platform_pmic_battery_driver = {
.driver = {
.name = DRIVER_NAME,
},
.probe = platform_pmic_battery_probe,
.remove = platform_pmic_battery_remove,
};
module_platform_driver(platform_pmic_battery_driver);
MODULE_AUTHOR("Nithish Mahalingam <nithish.mahalingam@intel.com>");
MODULE_DESCRIPTION("Intel Moorestown PMIC Battery Driver");
MODULE_LICENSE("GPL");
/*
* Maxim MAX14656 / AL32 USB Charger Detector driver
*
* Copyright (C) 2014 LG Electronics, Inc
* Copyright (C) 2016 Alexander Kurz <akurz@blala.de>
*
* Components from Maxim AL32 Charger detection Driver for MX50 Yoshi Board
* Copyright (C) Amazon Technologies Inc. All rights reserved.
* Manish Lachwani (lachwani@lab126.com)
*
* This package is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
#include <linux/workqueue.h>
#include <linux/power_supply.h>
#define MAX14656_MANUFACTURER "Maxim Integrated"
#define MAX14656_NAME "max14656"
#define MAX14656_DEVICE_ID 0x00
#define MAX14656_INTERRUPT_1 0x01
#define MAX14656_INTERRUPT_2 0x02
#define MAX14656_STATUS_1 0x03
#define MAX14656_STATUS_2 0x04
#define MAX14656_INTMASK_1 0x05
#define MAX14656_INTMASK_2 0x06
#define MAX14656_CONTROL_1 0x07
#define MAX14656_CONTROL_2 0x08
#define MAX14656_CONTROL_3 0x09
#define DEVICE_VENDOR_MASK 0xf0
#define DEVICE_REV_MASK 0x0f
#define INT_EN_REG_MASK BIT(4)
#define CHG_TYPE_INT_MASK BIT(0)
#define STATUS1_VB_VALID_MASK BIT(4)
#define STATUS1_CHG_TYPE_MASK 0xf
#define INT1_DCD_TIMEOUT_MASK BIT(7)
#define CONTROL1_DEFAULT 0x0d
#define CONTROL1_INT_EN BIT(4)
#define CONTROL1_INT_ACTIVE_HIGH BIT(5)
#define CONTROL1_EDGE BIT(7)
#define CONTROL2_DEFAULT 0x8e
#define CONTROL2_ADC_EN BIT(0)
#define CONTROL3_DEFAULT 0x8d
enum max14656_chg_type {
MAX14656_NO_CHARGER = 0,
MAX14656_SDP_CHARGER,
MAX14656_CDP_CHARGER,
MAX14656_DCP_CHARGER,
MAX14656_APPLE_500MA_CHARGER,
MAX14656_APPLE_1A_CHARGER,
MAX14656_APPLE_2A_CHARGER,
MAX14656_SPECIAL_500MA_CHARGER,
MAX14656_APPLE_12W,
MAX14656_CHARGER_LAST
};
static const struct max14656_chg_type_props {
enum power_supply_type type;
} chg_type_props[] = {
{ POWER_SUPPLY_TYPE_UNKNOWN },
{ POWER_SUPPLY_TYPE_USB },
{ POWER_SUPPLY_TYPE_USB_CDP },
{ POWER_SUPPLY_TYPE_USB_DCP },
{ POWER_SUPPLY_TYPE_USB_DCP },
{ POWER_SUPPLY_TYPE_USB_DCP },
{ POWER_SUPPLY_TYPE_USB_DCP },
{ POWER_SUPPLY_TYPE_USB_DCP },
{ POWER_SUPPLY_TYPE_USB },
};
struct max14656_chip {
struct i2c_client *client;
struct power_supply *detect_psy;
struct power_supply_desc psy_desc;
struct delayed_work irq_work;
int irq;
int online;
};
static int max14656_read_reg(struct i2c_client *client, int reg, u8 *val)
{
s32 ret;
ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0) {
dev_err(&client->dev,
"i2c read fail: can't read from %02x: %d\n",
reg, ret);
return ret;
}
*val = ret;
return 0;
}
static int max14656_write_reg(struct i2c_client *client, int reg, u8 val)
{
s32 ret;
ret = i2c_smbus_write_byte_data(client, reg, val);
if (ret < 0) {
dev_err(&client->dev,
"i2c write fail: can't write %02x to %02x: %d\n",
val, reg, ret);
return ret;
}
return 0;
}
static int max14656_read_block_reg(struct i2c_client *client, u8 reg,
u8 length, u8 *val)
{
int ret;
ret = i2c_smbus_read_i2c_block_data(client, reg, length, val);
if (ret < 0) {
dev_err(&client->dev, "failed to block read reg 0x%x: %d\n",
reg, ret);
return ret;
}
return 0;
}
#define REG_TOTAL_NUM 5
static void max14656_irq_worker(struct work_struct *work)
{
struct max14656_chip *chip =
container_of(work, struct max14656_chip, irq_work.work);
u8 buf[REG_TOTAL_NUM];
u8 chg_type;
int ret = 0;
ret = max14656_read_block_reg(chip->client, MAX14656_DEVICE_ID,
REG_TOTAL_NUM, buf);
if ((buf[MAX14656_STATUS_1] & STATUS1_VB_VALID_MASK) &&
(buf[MAX14656_STATUS_1] & STATUS1_CHG_TYPE_MASK)) {
chg_type = buf[MAX14656_STATUS_1] & STATUS1_CHG_TYPE_MASK;
if (chg_type < MAX14656_CHARGER_LAST)
chip->psy_desc.type = chg_type_props[chg_type].type;
else
chip->psy_desc.type = POWER_SUPPLY_TYPE_UNKNOWN;
chip->online = 1;
} else {
chip->online = 0;
chip->psy_desc.type = POWER_SUPPLY_TYPE_UNKNOWN;
}
power_supply_changed(chip->detect_psy);
}
static irqreturn_t max14656_irq(int irq, void *dev_id)
{
struct max14656_chip *chip = dev_id;
schedule_delayed_work(&chip->irq_work, msecs_to_jiffies(100));
return IRQ_HANDLED;
}
static int max14656_hw_init(struct max14656_chip *chip)
{
uint8_t val = 0;
uint8_t rev;
struct i2c_client *client = chip->client;
if (max14656_read_reg(client, MAX14656_DEVICE_ID, &val))
return -ENODEV;
if ((val & DEVICE_VENDOR_MASK) != 0x20) {
dev_err(&client->dev, "wrong vendor ID %d\n",
((val & DEVICE_VENDOR_MASK) >> 4));
return -ENODEV;
}
rev = val & DEVICE_REV_MASK;
/* Turn on ADC_EN */
if (max14656_write_reg(client, MAX14656_CONTROL_2, CONTROL2_ADC_EN))
return -EINVAL;
/* turn on interrupts and low power mode */
if (max14656_write_reg(client, MAX14656_CONTROL_1,
CONTROL1_DEFAULT |
CONTROL1_INT_EN |
CONTROL1_INT_ACTIVE_HIGH |
CONTROL1_EDGE))
return -EINVAL;
if (max14656_write_reg(client, MAX14656_INTMASK_1, 0x3))
return -EINVAL;
if (max14656_write_reg(client, MAX14656_INTMASK_2, 0x1))
return -EINVAL;
dev_info(&client->dev, "detected revision %d\n", rev);
return 0;
}
static int max14656_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct max14656_chip *chip = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
val->intval = chip->online;
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = MAX14656_NAME;
break;
case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = MAX14656_MANUFACTURER;
break;
default:
return -EINVAL;
}
return 0;
}
static enum power_supply_property max14656_battery_props[] = {
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
};
static int max14656_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
struct device *dev = &client->dev;
struct power_supply_config psy_cfg = {};
struct max14656_chip *chip;
int irq = client->irq;
int ret = 0;
if (irq <= 0) {
dev_err(dev, "invalid irq number: %d\n", irq);
return -ENODEV;
}
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(dev, "No support for SMBUS_BYTE_DATA\n");
return -ENODEV;
}
chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
psy_cfg.drv_data = chip;
chip->client = client;
chip->online = 0;
chip->psy_desc.name = MAX14656_NAME;
chip->psy_desc.type = POWER_SUPPLY_TYPE_UNKNOWN;
chip->psy_desc.properties = max14656_battery_props;
chip->psy_desc.num_properties = ARRAY_SIZE(max14656_battery_props);
chip->psy_desc.get_property = max14656_get_property;
chip->irq = irq;
ret = max14656_hw_init(chip);
if (ret)
return -ENODEV;
INIT_DELAYED_WORK(&chip->irq_work, max14656_irq_worker);
ret = devm_request_irq(dev, chip->irq, max14656_irq,
IRQF_TRIGGER_FALLING,
MAX14656_NAME, chip);
if (ret) {
dev_err(dev, "request_irq %d failed\n", chip->irq);
return -EINVAL;
}
enable_irq_wake(chip->irq);
chip->detect_psy = devm_power_supply_register(dev,
&chip->psy_desc, &psy_cfg);
if (IS_ERR(chip->detect_psy)) {
dev_err(dev, "power_supply_register failed\n");
return -EINVAL;
}
schedule_delayed_work(&chip->irq_work, msecs_to_jiffies(2000));
return 0;
}
static const struct i2c_device_id max14656_id[] = {
{ "max14656", 0 },
{}
};
MODULE_DEVICE_TABLE(i2c, max14656_id);
static const struct of_device_id max14656_match_table[] = {
{ .compatible = "maxim,max14656", },
{}
};
MODULE_DEVICE_TABLE(of, max14656_match_table);
static struct i2c_driver max14656_i2c_driver = {
.driver = {
.name = "max14656",
.of_match_table = max14656_match_table,
},
.probe = max14656_probe,
.id_table = max14656_id,
};
module_i2c_driver(max14656_i2c_driver);
MODULE_DESCRIPTION("MAX14656 USB charger detector");
MODULE_LICENSE("GPL v2");
......@@ -148,10 +148,8 @@ static int max8997_battery_probe(struct platform_device *pdev)
charger = devm_kzalloc(&pdev->dev, sizeof(struct charger_data),
GFP_KERNEL);
if (charger == NULL) {
dev_err(&pdev->dev, "Cannot allocate memory.\n");
if (!charger)
return -ENOMEM;
}
platform_set_drvdata(pdev, charger);
......@@ -161,7 +159,7 @@ static int max8997_battery_probe(struct platform_device *pdev)
psy_cfg.drv_data = charger;
charger->battery = power_supply_register(&pdev->dev,
charger->battery = devm_power_supply_register(&pdev->dev,
&max8997_battery_desc,
&psy_cfg);
if (IS_ERR(charger->battery)) {
......@@ -172,14 +170,6 @@ static int max8997_battery_probe(struct platform_device *pdev)
return 0;
}
static int max8997_battery_remove(struct platform_device *pdev)
{
struct charger_data *charger = platform_get_drvdata(pdev);
power_supply_unregister(charger->battery);
return 0;
}
static const struct platform_device_id max8997_battery_id[] = {
{ "max8997-battery", 0 },
{ }
......@@ -191,7 +181,6 @@ static struct platform_driver max8997_battery_driver = {
.name = "max8997-battery",
},
.probe = max8997_battery_probe,
.remove = max8997_battery_remove,
.id_table = max8997_battery_id,
};
......
......@@ -393,7 +393,6 @@ static int pcf50633_mbc_probe(struct platform_device *pdev)
{
struct power_supply_config psy_cfg = {};
struct pcf50633_mbc *mbc;
int ret;
int i;
u8 mbcs1;
......@@ -419,8 +418,7 @@ static int pcf50633_mbc_probe(struct platform_device *pdev)
&psy_cfg);
if (IS_ERR(mbc->adapter)) {
dev_err(mbc->pcf->dev, "failed to register adapter\n");
ret = PTR_ERR(mbc->adapter);
return ret;
return PTR_ERR(mbc->adapter);
}
mbc->usb = power_supply_register(&pdev->dev, &pcf50633_mbc_usb_desc,
......@@ -428,8 +426,7 @@ static int pcf50633_mbc_probe(struct platform_device *pdev)
if (IS_ERR(mbc->usb)) {
dev_err(mbc->pcf->dev, "failed to register usb\n");
power_supply_unregister(mbc->adapter);
ret = PTR_ERR(mbc->usb);
return ret;
return PTR_ERR(mbc->usb);
}
mbc->ac = power_supply_register(&pdev->dev, &pcf50633_mbc_ac_desc,
......@@ -438,12 +435,10 @@ static int pcf50633_mbc_probe(struct platform_device *pdev)
dev_err(mbc->pcf->dev, "failed to register ac\n");
power_supply_unregister(mbc->adapter);
power_supply_unregister(mbc->usb);
ret = PTR_ERR(mbc->ac);
return ret;
return PTR_ERR(mbc->ac);
}
ret = sysfs_create_group(&pdev->dev.kobj, &mbc_attr_group);
if (ret)
if (sysfs_create_group(&pdev->dev.kobj, &mbc_attr_group))
dev_err(mbc->pcf->dev, "failed to create sysfs entries\n");
mbcs1 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS1);
......
......@@ -35,6 +35,7 @@
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/extcon.h>
#include <linux/regulator/driver.h>
#define SMBB_CHG_VMAX 0x040
#define SMBB_CHG_VSAFE 0x041
......@@ -72,6 +73,8 @@
#define BTC_CTRL_HOT_EXT_N BIT(0)
#define SMBB_USB_IMAX 0x344
#define SMBB_USB_OTG_CTL 0x348
#define OTG_CTL_EN BIT(0)
#define SMBB_USB_ENUM_TIMER_STOP 0x34e
#define ENUM_TIMER_STOP BIT(0)
#define SMBB_USB_SEC_ACCESS 0x3d0
......@@ -125,6 +128,9 @@ struct smbb_charger {
struct power_supply *dc_psy;
struct power_supply *bat_psy;
struct regmap *regmap;
struct regulator_desc otg_rdesc;
struct regulator_dev *otg_reg;
};
static const unsigned int smbb_usb_extcon_cable[] = {
......@@ -378,7 +384,7 @@ static irqreturn_t smbb_usb_valid_handler(int irq, void *_data)
struct smbb_charger *chg = _data;
smbb_set_line_flag(chg, irq, STATUS_USBIN_VALID);
extcon_set_cable_state_(chg->edev, EXTCON_USB,
extcon_set_state_sync(chg->edev, EXTCON_USB,
chg->status & STATUS_USBIN_VALID);
power_supply_changed(chg->usb_psy);
......@@ -787,12 +793,56 @@ static const struct power_supply_desc dc_psy_desc = {
.property_is_writeable = smbb_charger_writable_property,
};
static int smbb_chg_otg_enable(struct regulator_dev *rdev)
{
struct smbb_charger *chg = rdev_get_drvdata(rdev);
int rc;
rc = regmap_update_bits(chg->regmap, chg->addr + SMBB_USB_OTG_CTL,
OTG_CTL_EN, OTG_CTL_EN);
if (rc)
dev_err(chg->dev, "failed to update OTG_CTL\n");
return rc;
}
static int smbb_chg_otg_disable(struct regulator_dev *rdev)
{
struct smbb_charger *chg = rdev_get_drvdata(rdev);
int rc;
rc = regmap_update_bits(chg->regmap, chg->addr + SMBB_USB_OTG_CTL,
OTG_CTL_EN, 0);
if (rc)
dev_err(chg->dev, "failed to update OTG_CTL\n");
return rc;
}
static int smbb_chg_otg_is_enabled(struct regulator_dev *rdev)
{
struct smbb_charger *chg = rdev_get_drvdata(rdev);
unsigned int value = 0;
int rc;
rc = regmap_read(chg->regmap, chg->addr + SMBB_USB_OTG_CTL, &value);
if (rc)
dev_err(chg->dev, "failed to read OTG_CTL\n");
return !!(value & OTG_CTL_EN);
}
static const struct regulator_ops smbb_chg_otg_ops = {
.enable = smbb_chg_otg_enable,
.disable = smbb_chg_otg_disable,
.is_enabled = smbb_chg_otg_is_enabled,
};
static int smbb_charger_probe(struct platform_device *pdev)
{
struct power_supply_config bat_cfg = {};
struct power_supply_config usb_cfg = {};
struct power_supply_config dc_cfg = {};
struct smbb_charger *chg;
struct regulator_config config = { };
int rc, i;
chg = devm_kzalloc(&pdev->dev, sizeof(*chg), GFP_KERNEL);
......@@ -905,6 +955,26 @@ static int smbb_charger_probe(struct platform_device *pdev)
}
}
/*
* otg regulator is used to control VBUS voltage direction
* when USB switches between host and gadget mode
*/
chg->otg_rdesc.id = -1;
chg->otg_rdesc.name = "otg-vbus";
chg->otg_rdesc.ops = &smbb_chg_otg_ops;
chg->otg_rdesc.owner = THIS_MODULE;
chg->otg_rdesc.type = REGULATOR_VOLTAGE;
chg->otg_rdesc.supply_name = "usb-otg-in";
chg->otg_rdesc.of_match = "otg-vbus";
config.dev = &pdev->dev;
config.driver_data = chg;
chg->otg_reg = devm_regulator_register(&pdev->dev, &chg->otg_rdesc,
&config);
if (IS_ERR(chg->otg_reg))
return PTR_ERR(chg->otg_reg);
chg->jeita_ext_temp = of_property_read_bool(pdev->dev.of_node,
"qcom,jeita-extended-temp-range");
......
/*
* Copyright (c) 2016, Prodys S.L.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This adds support for sbs-charger compilant chips as defined here:
* http://sbs-forum.org/specs/sbc110.pdf
*
* Implemetation based on sbs-battery.c
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/power_supply.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/regmap.h>
#include <linux/of_gpio.h>
#include <linux/bitops.h>
#define SBS_CHARGER_REG_SPEC_INFO 0x11
#define SBS_CHARGER_REG_STATUS 0x13
#define SBS_CHARGER_REG_ALARM_WARNING 0x16
#define SBS_CHARGER_STATUS_CHARGE_INHIBITED BIT(1)
#define SBS_CHARGER_STATUS_RES_COLD BIT(9)
#define SBS_CHARGER_STATUS_RES_HOT BIT(10)
#define SBS_CHARGER_STATUS_BATTERY_PRESENT BIT(14)
#define SBS_CHARGER_STATUS_AC_PRESENT BIT(15)
#define SBS_CHARGER_POLL_TIME 500
struct sbs_info {
struct i2c_client *client;
struct power_supply *power_supply;
struct regmap *regmap;
struct delayed_work work;
unsigned int last_state;
};
static int sbs_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct sbs_info *chip = power_supply_get_drvdata(psy);
unsigned int reg;
reg = chip->last_state;
switch (psp) {
case POWER_SUPPLY_PROP_PRESENT:
val->intval = !!(reg & SBS_CHARGER_STATUS_BATTERY_PRESENT);
break;
case POWER_SUPPLY_PROP_ONLINE:
val->intval = !!(reg & SBS_CHARGER_STATUS_AC_PRESENT);
break;
case POWER_SUPPLY_PROP_STATUS:
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
if (!(reg & SBS_CHARGER_STATUS_BATTERY_PRESENT))
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
else if (reg & SBS_CHARGER_STATUS_AC_PRESENT &&
!(reg & SBS_CHARGER_STATUS_CHARGE_INHIBITED))
val->intval = POWER_SUPPLY_STATUS_CHARGING;
else
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
break;
case POWER_SUPPLY_PROP_HEALTH:
if (reg & SBS_CHARGER_STATUS_RES_COLD)
val->intval = POWER_SUPPLY_HEALTH_COLD;
if (reg & SBS_CHARGER_STATUS_RES_HOT)
val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
else
val->intval = POWER_SUPPLY_HEALTH_GOOD;
break;
default:
return -EINVAL;
}
return 0;
}
static int sbs_check_state(struct sbs_info *chip)
{
unsigned int reg;
int ret;
ret = regmap_read(chip->regmap, SBS_CHARGER_REG_STATUS, &reg);
if (!ret && reg != chip->last_state) {
chip->last_state = reg;
power_supply_changed(chip->power_supply);
return 1;
}
return 0;
}
static void sbs_delayed_work(struct work_struct *work)
{
struct sbs_info *chip = container_of(work, struct sbs_info, work.work);
sbs_check_state(chip);
schedule_delayed_work(&chip->work,
msecs_to_jiffies(SBS_CHARGER_POLL_TIME));
}
static irqreturn_t sbs_irq_thread(int irq, void *data)
{
struct sbs_info *chip = data;
int ret;
ret = sbs_check_state(chip);
return ret ? IRQ_HANDLED : IRQ_NONE;
}
static enum power_supply_property sbs_properties[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_HEALTH,
};
static bool sbs_readable_reg(struct device *dev, unsigned int reg)
{
if (reg < SBS_CHARGER_REG_SPEC_INFO)
return false;
else
return true;
}
static bool sbs_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case SBS_CHARGER_REG_STATUS:
return true;
}
return false;
}
static const struct regmap_config sbs_regmap = {
.reg_bits = 8,
.val_bits = 16,
.max_register = SBS_CHARGER_REG_ALARM_WARNING,
.readable_reg = sbs_readable_reg,
.volatile_reg = sbs_volatile_reg,
.val_format_endian = REGMAP_ENDIAN_LITTLE, /* since based on SMBus */
};
static const struct power_supply_desc sbs_desc = {
.name = "sbs-charger",
.type = POWER_SUPPLY_TYPE_MAINS,
.properties = sbs_properties,
.num_properties = ARRAY_SIZE(sbs_properties),
.get_property = sbs_get_property,
};
static int sbs_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct power_supply_config psy_cfg = {};
struct sbs_info *chip;
int ret, val;
chip = devm_kzalloc(&client->dev, sizeof(struct sbs_info), GFP_KERNEL);
if (!chip)
return -ENOMEM;
chip->client = client;
psy_cfg.of_node = client->dev.of_node;
psy_cfg.drv_data = chip;
i2c_set_clientdata(client, chip);
chip->regmap = devm_regmap_init_i2c(client, &sbs_regmap);
if (IS_ERR(chip->regmap))
return PTR_ERR(chip->regmap);
/*
* Before we register, we need to make sure we can actually talk
* to the battery.
*/
ret = regmap_read(chip->regmap, SBS_CHARGER_REG_STATUS, &val);
if (ret) {
dev_err(&client->dev, "Failed to get device status\n");
return ret;
}
chip->last_state = val;
chip->power_supply = devm_power_supply_register(&client->dev, &sbs_desc,
&psy_cfg);
if (IS_ERR(chip->power_supply)) {
dev_err(&client->dev, "Failed to register power supply\n");
return PTR_ERR(chip->power_supply);
}
/*
* The sbs-charger spec doesn't impose the use of an interrupt. So in
* the case it wasn't provided we use polling in order get the charger's
* status.
*/
if (client->irq) {
ret = devm_request_threaded_irq(&client->dev, client->irq,
NULL, sbs_irq_thread,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
dev_name(&client->dev), chip);
if (ret) {
dev_err(&client->dev, "Failed to request irq, %d\n", ret);
return ret;
}
} else {
INIT_DELAYED_WORK(&chip->work, sbs_delayed_work);
schedule_delayed_work(&chip->work,
msecs_to_jiffies(SBS_CHARGER_POLL_TIME));
}
dev_info(&client->dev,
"%s: smart charger device registered\n", client->name);
return 0;
}
static int sbs_remove(struct i2c_client *client)
{
struct sbs_info *chip = i2c_get_clientdata(client);
cancel_delayed_work_sync(&chip->work);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id sbs_dt_ids[] = {
{ .compatible = "sbs,sbs-charger" },
{ },
};
MODULE_DEVICE_TABLE(of, sbs_dt_ids);
#endif
static const struct i2c_device_id sbs_id[] = {
{ "sbs-charger", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, sbs_id);
static struct i2c_driver sbs_driver = {
.probe = sbs_probe,
.remove = sbs_remove,
.id_table = sbs_id,
.driver = {
.name = "sbs-charger",
.of_match_table = of_match_ptr(sbs_dt_ids),
},
};
module_i2c_driver(sbs_driver);
MODULE_AUTHOR("Nicolas Saenz Julienne <nicolassaenzj@gmail.com>");
MODULE_DESCRIPTION("SBS smart charger driver");
MODULE_LICENSE("GPL v2");
......@@ -35,22 +35,22 @@
#include <linux/mfd/core.h>
#include <linux/mfd/tps65217.h>
#define CHARGER_STATUS_PRESENT (TPS65217_STATUS_ACPWR | TPS65217_STATUS_USBPWR)
#define NUM_CHARGER_IRQS 2
#define POLL_INTERVAL (HZ * 2)
struct tps65217_charger {
struct tps65217 *tps;
struct device *dev;
struct power_supply *ac;
struct power_supply *psy;
int ac_online;
int prev_ac_online;
int online;
int prev_online;
struct task_struct *poll_task;
int irq;
};
static enum power_supply_property tps65217_ac_props[] = {
static enum power_supply_property tps65217_charger_props[] = {
POWER_SUPPLY_PROP_ONLINE,
};
......@@ -95,7 +95,7 @@ static int tps65217_enable_charging(struct tps65217_charger *charger)
int ret;
/* charger already enabled */
if (charger->ac_online)
if (charger->online)
return 0;
dev_dbg(charger->dev, "%s: enable charging\n", __func__);
......@@ -110,19 +110,19 @@ static int tps65217_enable_charging(struct tps65217_charger *charger)
return ret;
}
charger->ac_online = 1;
charger->online = 1;
return 0;
}
static int tps65217_ac_get_property(struct power_supply *psy,
static int tps65217_charger_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct tps65217_charger *charger = power_supply_get_drvdata(psy);
if (psp == POWER_SUPPLY_PROP_ONLINE) {
val->intval = charger->ac_online;
val->intval = charger->online;
return 0;
}
return -EINVAL;
......@@ -133,7 +133,7 @@ static irqreturn_t tps65217_charger_irq(int irq, void *dev)
int ret, val;
struct tps65217_charger *charger = dev;
charger->prev_ac_online = charger->ac_online;
charger->prev_online = charger->online;
ret = tps65217_reg_read(charger->tps, TPS65217_REG_STATUS, &val);
if (ret < 0) {
......@@ -144,8 +144,8 @@ static irqreturn_t tps65217_charger_irq(int irq, void *dev)
dev_dbg(charger->dev, "%s: 0x%x\n", __func__, val);
/* check for AC status bit */
if (val & TPS65217_STATUS_ACPWR) {
/* check for charger status bit */
if (val & CHARGER_STATUS_PRESENT) {
ret = tps65217_enable_charging(charger);
if (ret) {
dev_err(charger->dev,
......@@ -153,11 +153,11 @@ static irqreturn_t tps65217_charger_irq(int irq, void *dev)
return IRQ_HANDLED;
}
} else {
charger->ac_online = 0;
charger->online = 0;
}
if (charger->prev_ac_online != charger->ac_online)
power_supply_changed(charger->ac);
if (charger->prev_online != charger->online)
power_supply_changed(charger->psy);
ret = tps65217_reg_read(charger->tps, TPS65217_REG_CHGCONFIG0, &val);
if (ret < 0) {
......@@ -188,11 +188,11 @@ static int tps65217_charger_poll_task(void *data)
}
static const struct power_supply_desc tps65217_charger_desc = {
.name = "tps65217-ac",
.name = "tps65217-charger",
.type = POWER_SUPPLY_TYPE_MAINS,
.get_property = tps65217_ac_get_property,
.properties = tps65217_ac_props,
.num_properties = ARRAY_SIZE(tps65217_ac_props),
.get_property = tps65217_charger_get_property,
.properties = tps65217_charger_props,
.num_properties = ARRAY_SIZE(tps65217_charger_props),
};
static int tps65217_charger_probe(struct platform_device *pdev)
......@@ -200,8 +200,10 @@ static int tps65217_charger_probe(struct platform_device *pdev)
struct tps65217 *tps = dev_get_drvdata(pdev->dev.parent);
struct tps65217_charger *charger;
struct power_supply_config cfg = {};
int irq;
struct task_struct *poll_task;
int irq[NUM_CHARGER_IRQS];
int ret;
int i;
dev_dbg(&pdev->dev, "%s\n", __func__);
......@@ -216,18 +218,16 @@ static int tps65217_charger_probe(struct platform_device *pdev)
cfg.of_node = pdev->dev.of_node;
cfg.drv_data = charger;
charger->ac = devm_power_supply_register(&pdev->dev,
charger->psy = devm_power_supply_register(&pdev->dev,
&tps65217_charger_desc,
&cfg);
if (IS_ERR(charger->ac)) {
if (IS_ERR(charger->psy)) {
dev_err(&pdev->dev, "failed: power supply register\n");
return PTR_ERR(charger->ac);
return PTR_ERR(charger->psy);
}
irq = platform_get_irq_byname(pdev, "AC");
if (irq < 0)
irq = -ENXIO;
charger->irq = irq;
irq[0] = platform_get_irq_byname(pdev, "USB");
irq[1] = platform_get_irq_byname(pdev, "AC");
ret = tps65217_config_charger(charger);
if (ret < 0) {
......@@ -235,29 +235,36 @@ static int tps65217_charger_probe(struct platform_device *pdev)
return ret;
}
if (irq != -ENXIO) {
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
/* Create a polling thread if an interrupt is invalid */
if (irq[0] < 0 || irq[1] < 0) {
poll_task = kthread_run(tps65217_charger_poll_task,
charger, "ktps65217charger");
if (IS_ERR(poll_task)) {
ret = PTR_ERR(poll_task);
dev_err(charger->dev,
"Unable to run kthread err %d\n", ret);
return ret;
}
charger->poll_task = poll_task;
return 0;
}
/* Create IRQ threads for charger interrupts */
for (i = 0; i < NUM_CHARGER_IRQS; i++) {
ret = devm_request_threaded_irq(&pdev->dev, irq[i], NULL,
tps65217_charger_irq,
0, "tps65217-charger",
charger);
if (ret) {
dev_err(charger->dev,
"Unable to register irq %d err %d\n", irq,
"Unable to register irq %d err %d\n", irq[i],
ret);
return ret;
}
/* Check current state */
tps65217_charger_irq(irq, charger);
} else {
charger->poll_task = kthread_run(tps65217_charger_poll_task,
charger, "ktps65217charger");
if (IS_ERR(charger->poll_task)) {
ret = PTR_ERR(charger->poll_task);
dev_err(charger->dev,
"Unable to run kthread err %d\n", ret);
return ret;
}
tps65217_charger_irq(-1, charger);
}
return 0;
......@@ -267,7 +274,7 @@ static int tps65217_charger_remove(struct platform_device *pdev)
{
struct tps65217_charger *charger = platform_get_drvdata(pdev);
if (charger->irq == -ENXIO)
if (charger->poll_task)
kthread_stop(charger->poll_task);
return 0;
......
......@@ -175,11 +175,6 @@ static int wm97xx_bat_probe(struct platform_device *dev)
if (dev->id != -1)
return -EINVAL;
if (!pdata) {
dev_err(&dev->dev, "No platform_data supplied\n");
return -EINVAL;
}
if (gpio_is_valid(pdata->charge_gpio)) {
ret = gpio_request(pdata->charge_gpio, "BATT CHRG");
if (ret)
......
......@@ -13,7 +13,7 @@
#include <linux/regmap.h>
enum {
enum axp20x_variants {
AXP152_ID = 0,
AXP202_ID,
AXP209_ID,
......@@ -532,35 +532,6 @@ struct axp20x_dev {
const struct regmap_irq_chip *regmap_irq_chip;
};
#define BATTID_LEN 64
#define OCV_CURVE_SIZE 32
#define MAX_THERM_CURVE_SIZE 25
#define PD_DEF_MIN_TEMP 0
#define PD_DEF_MAX_TEMP 55
struct axp20x_fg_pdata {
char battid[BATTID_LEN + 1];
int design_cap;
int min_volt;
int max_volt;
int max_temp;
int min_temp;
int cap1;
int cap0;
int rdc1;
int rdc0;
int ocv_curve[OCV_CURVE_SIZE];
int tcsz;
int thermistor_curve[MAX_THERM_CURVE_SIZE][2];
};
struct axp20x_chrg_pdata {
int max_cc;
int max_cv;
int def_cc;
int def_cv;
};
struct axp288_extcon_pdata {
/* GPIO pin control to switch D+/D- lines b/w PMIC and SOC */
struct gpio_desc *gpio_mux_cntl;
......
......@@ -4,8 +4,16 @@
enum bq27xxx_chip {
BQ27000 = 1, /* bq27000, bq27200 */
BQ27010, /* bq27010, bq27210 */
BQ27500, /* bq27500 */
BQ27510, /* bq27510, bq27520 */
BQ2750X, /* bq27500 deprecated alias */
BQ2751X, /* bq27510, bq27520 deprecated alias */
BQ27500, /* bq27500/1 */
BQ27510G1, /* bq27510G1 */
BQ27510G2, /* bq27510G2 */
BQ27510G3, /* bq27510G3 */
BQ27520G1, /* bq27520G1 */
BQ27520G2, /* bq27520G2 */
BQ27520G3, /* bq27520G3 */
BQ27520G4, /* bq27520G4 */
BQ27530, /* bq27530, bq27531 */
BQ27541, /* bq27541, bq27542, bq27546, bq27742 */
BQ27545, /* bq27545 */
......
......@@ -81,6 +81,7 @@
#define AT91_DDRSDRC_LPCB_POWER_DOWN 2
#define AT91_DDRSDRC_LPCB_DEEP_POWER_DOWN 3
#define AT91_DDRSDRC_CLKFR (1 << 2) /* Clock Frozen */
#define AT91_DDRSDRC_LPDDR2_PWOFF (1 << 3) /* LPDDR Power Off */
#define AT91_DDRSDRC_PASR (7 << 4) /* Partial Array Self Refresh */
#define AT91_DDRSDRC_TCSR (3 << 8) /* Temperature Compensated Self Refresh */
#define AT91_DDRSDRC_DS (3 << 10) /* Drive Strength */
......@@ -96,7 +97,9 @@
#define AT91_DDRSDRC_MD_SDR 0
#define AT91_DDRSDRC_MD_LOW_POWER_SDR 1
#define AT91_DDRSDRC_MD_LOW_POWER_DDR 3
#define AT91_DDRSDRC_MD_LPDDR3 5
#define AT91_DDRSDRC_MD_DDR2 6 /* [SAM9 Only] */
#define AT91_DDRSDRC_MD_LPDDR2 7
#define AT91_DDRSDRC_DBW (1 << 4) /* Data Bus Width */
#define AT91_DDRSDRC_DBW_32BITS (0 << 4)
#define AT91_DDRSDRC_DBW_16BITS (1 << 4)
......
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