power supply and reset changes for the v4.4 series

* new AXP20X USB Power driver
  * new Qualcomm SMBB driver
  * new TPS65217 Charger driver
  * BQ24257: add BQ24250/BQ24251 support
  * overhaul bq27x00 battery driver, rename to bq27xxx
  * misc. fixes and cleanups
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQIcBAABCgAGBQJWOn8oAAoJENju1/PIO/qahrEP/RPHXvTxtYv8YvhaFbqcr07x
 11gRB8RRthKK5woC3s70QKlMtcHijFeCwKuacut+7tfgK2oZDIm+Bd43Pl93aypU
 Yk4oTwop5OyrLsGIGiTIPPV9HTwr4q69Or2Q91mqUAUe2wGB5uCuJiJmCowUwfZs
 piv+ISLSOcH+i1bTx3hQ7h7axsdAlaSz2o1JprZaxm3rM67M8zF1ZHSK7wlpAeBa
 DiDrtUvEDCeLL8dmCA9xkFV4yFB9T+KNh0730N2RY+zlZS6WjSpzryqwsUWWyQ6h
 U9gFgcs+DjVaGHuMsIX2zm7xXfXHgUBtXdSQoCBhpSrZsdufMsT/8VQqVZ0yqpXL
 pQ2EXy63k6tXnXEyAKTA0tAOX/WZNfmmNblkea4JiyZULn7b/WH09waMZUBJqe84
 Yn3SatVdGDN4nawsivzdiM3zR9/WTE9R3DmEeAE+g1RRsSraNwWlkzpDXcmnWmbc
 DoprnjLsDJfqL3/1ysF2V2I4B26Bws73qq6UUjJ4KEGWf8Y1fVawxrbcYmnrvg1K
 zW/saEgtl70FYqtee/0lIqTpjOZRX/O6QH5i8p7VC8cUzftX0Twc8MvfvpamDywX
 Cr1Y7pIMEhc6TFUP5lOzx9xJelA5PHJMpAT+02fh6YXMAzCcBUzL113rY5zBrVVl
 gce0iHDKAybmvww77z1r
 =0be4
 -----END PGP SIGNATURE-----

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

Pull power supply and reset updates from Sebastian Reichel:
 - new AXP20X USB Power driver
 - new Qualcomm SMBB driver
 - new TPS65217 Charger driver
 - BQ24257: add BQ24250/BQ24251 support
 - overhaul bq27x00 battery driver, rename to bq27xxx
 - misc fixes and cleanups

* tag 'for-v4.4' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (54 commits)
  power: bq27xxx_battery: Remove unneeded dependency in Kconfig
  power: bq27xxx_battery: move irq handler to i2c section
  power: bq27xxx_battery: fix platform probe
  twl4030_charger: add missing iio dependency
  power_supply: charger-manager: add missing of_node_put
  Documentation: power: bq24257: Document exported sysfs entries
  power: bq24257: Add various device-specific sysfs properties
  power: bq24257: Allow input current limit sysfs access
  power: bq24257: Add input DPM voltage threshold setting support
  power: bq24257: Add over voltage protection setting support
  power: bq24257: Add SW-based approach for Power Good determination
  power: bq24257: Allow manual setting of input current limit
  power: bq24257: Add bit definition for temp sense enable
  power: bq24257: Add basic support for bq24250/bq24251
  dt: power: bq24257-charger: Cover additional devices
  power: bq24257: Simplify bq24257_power_supply_init()
  power: bq24257: Use managed power supply register
  power: bq24257: Streamline input current limit setup
  power: bq24257: Remove IRQ config through stat-gpios
  power: bq27xxx_battery: fix signedness bug in bq27xxx_battery_read_health()
  ...
This commit is contained in:
Linus Torvalds 2015-11-05 12:28:15 -08:00
commit 400c5bd5a5
39 changed files with 3818 additions and 1474 deletions

View file

@ -954,46 +954,32 @@ static int pm860x_battery_probe(struct platform_device *pdev)
else
info->resistor = 300; /* set default internal resistor */
info->battery = power_supply_register(&pdev->dev, &pm860x_battery_desc,
NULL);
info->battery = devm_power_supply_register(&pdev->dev,
&pm860x_battery_desc,
NULL);
if (IS_ERR(info->battery))
return PTR_ERR(info->battery);
info->battery->dev.parent = &pdev->dev;
ret = request_threaded_irq(info->irq_cc, NULL,
pm860x_coulomb_handler, IRQF_ONESHOT,
"coulomb", info);
ret = devm_request_threaded_irq(chip->dev, info->irq_cc, NULL,
pm860x_coulomb_handler, IRQF_ONESHOT,
"coulomb", info);
if (ret < 0) {
dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n",
info->irq_cc, ret);
goto out_reg;
return ret;
}
ret = request_threaded_irq(info->irq_batt, NULL, pm860x_batt_handler,
IRQF_ONESHOT, "battery", info);
ret = devm_request_threaded_irq(chip->dev, info->irq_batt, NULL,
pm860x_batt_handler,
IRQF_ONESHOT, "battery", info);
if (ret < 0) {
dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n",
info->irq_batt, ret);
goto out_coulomb;
return ret;
}
return 0;
out_coulomb:
free_irq(info->irq_cc, info);
out_reg:
power_supply_unregister(info->battery);
return ret;
}
static int pm860x_battery_remove(struct platform_device *pdev)
{
struct pm860x_battery_info *info = platform_get_drvdata(pdev);
free_irq(info->irq_batt, info);
free_irq(info->irq_cc, info);
power_supply_unregister(info->battery);
return 0;
}
@ -1028,7 +1014,6 @@ static struct platform_driver pm860x_battery_driver = {
.pm = &pm860x_battery_pm_ops,
},
.probe = pm860x_battery_probe,
.remove = pm860x_battery_remove,
};
module_platform_driver(pm860x_battery_driver);

View file

@ -157,26 +157,25 @@ config BATTERY_SBS
Say Y to include support for SBS battery driver for SBS-compliant
gas gauges.
config BATTERY_BQ27x00
tristate "BQ27x00 battery driver"
depends on I2C || I2C=n
config BATTERY_BQ27XXX
tristate "BQ27xxx battery driver"
help
Say Y here to enable support for batteries with BQ27x00 (I2C/HDQ) chips.
Say Y here to enable support for batteries with BQ27xxx (I2C/HDQ) chips.
config BATTERY_BQ27X00_I2C
bool "BQ27200/BQ27500 support"
depends on BATTERY_BQ27x00
config BATTERY_BQ27XXX_I2C
bool "BQ27xxx I2C support"
depends on BATTERY_BQ27XXX
depends on I2C
default y
help
Say Y here to enable support for batteries with BQ27x00 (I2C) chips.
Say Y here to enable support for batteries with BQ27xxx (I2C) chips.
config BATTERY_BQ27X00_PLATFORM
bool "BQ27000 support"
depends on BATTERY_BQ27x00
config BATTERY_BQ27XXX_PLATFORM
bool "BQ27xxx HDQ support"
depends on BATTERY_BQ27XXX
default y
help
Say Y here to enable support for batteries with BQ27000 (HDQ) chips.
Say Y here to enable support for batteries with BQ27xxx (HDQ) chips.
config BATTERY_DA9030
tristate "DA9030 battery driver"
@ -313,7 +312,7 @@ config CHARGER_MAX8903
config CHARGER_TWL4030
tristate "OMAP TWL4030 BCI charger driver"
depends on TWL4030_CORE
depends on IIO && TWL4030_CORE
help
Say Y here to enable support for TWL4030 Battery Charge Interface.
@ -379,6 +378,18 @@ config CHARGER_MAX8998
Say Y to enable support for the battery charger control sysfs and
platform data of MAX8998/LP3974 PMICs.
config CHARGER_QCOM_SMBB
tristate "Qualcomm Switch-Mode Battery Charger and Boost"
depends on MFD_SPMI_PMIC || COMPILE_TEST
depends on OF
help
Say Y to include support for the Switch-Mode Battery Charger and
Boost (SMBB) hardware found in Qualcomm PM8941 PMICs. The charger
is an integrated, single-cell lithium-ion battery charger. DT
configuration is required for loading, see the devicetree
documentation for more detail. The base name for this driver is
'pm8941_charger'.
config CHARGER_BQ2415X
tristate "TI BQ2415x battery charger driver"
depends on I2C
@ -397,12 +408,13 @@ config CHARGER_BQ24190
Say Y to enable support for the TI BQ24190 battery charger.
config CHARGER_BQ24257
tristate "TI BQ24257 battery charger driver"
tristate "TI BQ24250/24251/24257 battery charger driver"
depends on I2C
depends on GPIOLIB || COMPILE_TEST
depends on REGMAP_I2C
help
Say Y to enable support for the TI BQ24257 battery charger.
Say Y to enable support for the TI BQ24250, BQ24251, and BQ24257 battery
chargers.
config CHARGER_BQ24735
tristate "TI BQ24735 battery charger support"
@ -434,6 +446,13 @@ config CHARGER_TPS65090
Say Y here to enable support for battery charging with TPS65090
PMIC chips.
config CHARGER_TPS65217
tristate "TPS65217 battery charger driver"
depends on MFD_TPS65217
help
Say Y here to enable support for battery charging with TPS65217
PMIC chips.
config BATTERY_GAUGE_LTC2941
tristate "LTC2941/LTC2943 Battery Gauge Driver"
depends on I2C
@ -472,6 +491,13 @@ config CHARGER_RT9455
help
Say Y to enable support for Richtek RT9455 battery charger.
config AXP20X_POWER
tristate "AXP20x power supply driver"
depends on MFD_AXP20X
help
This driver provides support for the power supply features of
AXP20x PMIC.
source "drivers/power/reset/Kconfig"
endif # POWER_SUPPLY

View file

@ -9,6 +9,7 @@ obj-$(CONFIG_GENERIC_ADC_BATTERY) += generic-adc-battery.o
obj-$(CONFIG_PDA_POWER) += pda_power.o
obj-$(CONFIG_APM_POWER) += apm_power.o
obj-$(CONFIG_AXP20X_POWER) += axp20x_usb_power.o
obj-$(CONFIG_MAX8925_POWER) += max8925_power.o
obj-$(CONFIG_WM831X_BACKUP) += wm831x_backup.o
obj-$(CONFIG_WM831X_POWER) += wm831x_power.o
@ -29,7 +30,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_BATTERY_BQ27x00) += bq27x00_battery.o
obj-$(CONFIG_BATTERY_BQ27XXX) += bq27xxx_battery.o
obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o
obj-$(CONFIG_BATTERY_DA9052) += da9052-battery.o
obj-$(CONFIG_CHARGER_DA9150) += da9150-charger.o
@ -57,6 +58,7 @@ obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o
obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o
obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o
obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o
obj-$(CONFIG_CHARGER_QCOM_SMBB) += qcom_smbb.o
obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o
obj-$(CONFIG_CHARGER_BQ24190) += bq24190_charger.o
obj-$(CONFIG_CHARGER_BQ24257) += bq24257_charger.o
@ -65,6 +67,7 @@ obj-$(CONFIG_CHARGER_BQ25890) += bq25890_charger.o
obj-$(CONFIG_POWER_AVS) += avs/
obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o
obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o
obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o
obj-$(CONFIG_POWER_RESET) += reset/
obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o
obj-$(CONFIG_AXP288_CHARGER) += axp288_charger.o

View file

@ -0,0 +1,248 @@
/*
* AXP20x PMIC USB power supply status driver
*
* Copyright (C) 2015 Hans de Goede <hdegoede@redhat.com>
* Copyright (C) 2014 Bruno Prémont <bonbons@linux-vserver.org>
*
* 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/platform_device.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#define DRVNAME "axp20x-usb-power-supply"
#define AXP20X_PWR_STATUS_VBUS_PRESENT BIT(5)
#define AXP20X_PWR_STATUS_VBUS_USED BIT(4)
#define AXP20X_USB_STATUS_VBUS_VALID BIT(2)
#define AXP20X_VBUS_VHOLD_uV(b) (4000000 + (((b) >> 3) & 7) * 100000)
#define AXP20X_VBUS_CLIMIT_MASK 3
#define AXP20X_VBUC_CLIMIT_900mA 0
#define AXP20X_VBUC_CLIMIT_500mA 1
#define AXP20X_VBUC_CLIMIT_100mA 2
#define AXP20X_VBUC_CLIMIT_NONE 3
#define AXP20X_ADC_EN1_VBUS_CURR BIT(2)
#define AXP20X_ADC_EN1_VBUS_VOLT BIT(3)
#define AXP20X_VBUS_MON_VBUS_VALID BIT(3)
struct axp20x_usb_power {
struct regmap *regmap;
struct power_supply *supply;
};
static irqreturn_t axp20x_usb_power_irq(int irq, void *devid)
{
struct axp20x_usb_power *power = devid;
power_supply_changed(power->supply);
return IRQ_HANDLED;
}
static int axp20x_usb_power_get_property(struct power_supply *psy,
enum power_supply_property psp, union power_supply_propval *val)
{
struct axp20x_usb_power *power = power_supply_get_drvdata(psy);
unsigned int input, v;
int ret;
switch (psp) {
case POWER_SUPPLY_PROP_VOLTAGE_MIN:
ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v);
if (ret)
return ret;
val->intval = AXP20X_VBUS_VHOLD_uV(v);
return 0;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
ret = axp20x_read_variable_width(power->regmap,
AXP20X_VBUS_V_ADC_H, 12);
if (ret < 0)
return ret;
val->intval = ret * 1700; /* 1 step = 1.7 mV */
return 0;
case POWER_SUPPLY_PROP_CURRENT_MAX:
ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v);
if (ret)
return ret;
switch (v & AXP20X_VBUS_CLIMIT_MASK) {
case AXP20X_VBUC_CLIMIT_100mA:
val->intval = 100000;
break;
case AXP20X_VBUC_CLIMIT_500mA:
val->intval = 500000;
break;
case AXP20X_VBUC_CLIMIT_900mA:
val->intval = 900000;
break;
case AXP20X_VBUC_CLIMIT_NONE:
val->intval = -1;
break;
}
return 0;
case POWER_SUPPLY_PROP_CURRENT_NOW:
ret = axp20x_read_variable_width(power->regmap,
AXP20X_VBUS_I_ADC_H, 12);
if (ret < 0)
return ret;
val->intval = ret * 375; /* 1 step = 0.375 mA */
return 0;
default:
break;
}
/* All the properties below need the input-status reg value */
ret = regmap_read(power->regmap, AXP20X_PWR_INPUT_STATUS, &input);
if (ret)
return ret;
switch (psp) {
case POWER_SUPPLY_PROP_HEALTH:
if (!(input & AXP20X_PWR_STATUS_VBUS_PRESENT)) {
val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
break;
}
ret = regmap_read(power->regmap, AXP20X_USB_OTG_STATUS, &v);
if (ret)
return ret;
if (!(v & AXP20X_USB_STATUS_VBUS_VALID)) {
val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
break;
}
val->intval = POWER_SUPPLY_HEALTH_GOOD;
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = !!(input & AXP20X_PWR_STATUS_VBUS_PRESENT);
break;
case POWER_SUPPLY_PROP_ONLINE:
val->intval = !!(input & AXP20X_PWR_STATUS_VBUS_USED);
break;
default:
return -EINVAL;
}
return 0;
}
static enum power_supply_property axp20x_usb_power_properties[] = {
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_VOLTAGE_MIN,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_MAX,
POWER_SUPPLY_PROP_CURRENT_NOW,
};
static const struct power_supply_desc axp20x_usb_power_desc = {
.name = "axp20x-usb",
.type = POWER_SUPPLY_TYPE_USB,
.properties = axp20x_usb_power_properties,
.num_properties = ARRAY_SIZE(axp20x_usb_power_properties),
.get_property = axp20x_usb_power_get_property,
};
static int axp20x_usb_power_probe(struct platform_device *pdev)
{
struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
struct power_supply_config psy_cfg = {};
struct axp20x_usb_power *power;
static const char * const irq_names[] = { "VBUS_PLUGIN",
"VBUS_REMOVAL", "VBUS_VALID", "VBUS_NOT_VALID" };
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;
power->regmap = axp20x->regmap;
/* Enable vbus valid checking */
ret = regmap_update_bits(power->regmap, AXP20X_VBUS_MON,
AXP20X_VBUS_MON_VBUS_VALID, AXP20X_VBUS_MON_VBUS_VALID);
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 (ret)
return ret;
psy_cfg.of_node = pdev->dev.of_node;
psy_cfg.drv_data = power;
power->supply = devm_power_supply_register(&pdev->dev,
&axp20x_usb_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; i < ARRAY_SIZE(irq_names); 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_usb_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_usb_power_match[] = {
{ .compatible = "x-powers,axp202-usb-power-supply" },
{ }
};
MODULE_DEVICE_TABLE(of, axp20x_usb_power_match);
static struct platform_driver axp20x_usb_power_driver = {
.probe = axp20x_usb_power_probe,
.driver = {
.name = DRVNAME,
.of_match_table = axp20x_usb_power_match,
},
};
module_platform_driver(axp20x_usb_power_driver);
MODULE_AUTHOR("Hans de Goede <[email protected]>");
MODULE_DESCRIPTION("AXP20x PMIC USB power supply status driver");
MODULE_LICENSE("GPL");

View file

@ -1704,7 +1704,7 @@ error_4:
error_3:
bq2415x_power_supply_exit(bq);
error_2:
if (bq->notify_node)
if (bq && bq->notify_node)
of_node_put(bq->notify_node);
kfree(name);
error_1:

View file

@ -1543,5 +1543,4 @@ module_i2c_driver(bq24190_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mark A. Greer <[email protected]>");
MODULE_ALIAS("i2c:bq24190-charger");
MODULE_DESCRIPTION("TI BQ24190 Charger Driver");

View file

@ -13,6 +13,10 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Datasheets:
* http://www.ti.com/product/bq24250
* http://www.ti.com/product/bq24251
* http://www.ti.com/product/bq24257
*/
#include <linux/module.h>
@ -36,18 +40,33 @@
#define BQ24257_REG_7 0x06
#define BQ24257_MANUFACTURER "Texas Instruments"
#define BQ24257_STAT_IRQ "stat"
#define BQ24257_PG_GPIO "pg"
#define BQ24257_ILIM_SET_DELAY 1000 /* msec */
/*
* When adding support for new devices make sure that enum bq2425x_chip and
* bq2425x_chip_name[] always stay in sync!
*/
enum bq2425x_chip {
BQ24250,
BQ24251,
BQ24257,
};
static const char *const bq2425x_chip_name[] = {
"bq24250",
"bq24251",
"bq24257",
};
enum bq24257_fields {
F_WD_FAULT, F_WD_EN, F_STAT, F_FAULT, /* REG 1 */
F_RESET, F_IILIMIT, F_EN_STAT, F_EN_TERM, F_CE, F_HZ_MODE, /* REG 2 */
F_VBAT, F_USB_DET, /* REG 3 */
F_ICHG, F_ITERM, /* REG 4 */
F_LOOP_STATUS, F_LOW_CHG, F_DPDM_EN, F_CE_STATUS, F_VINDPM, /* REG 5 */
F_X2_TMR_EN, F_TMR, F_SYSOFF, F_TS_STAT, /* REG 6 */
F_X2_TMR_EN, F_TMR, F_SYSOFF, F_TS_EN, F_TS_STAT, /* REG 6 */
F_VOVP, F_CLR_VDP, F_FORCE_BATDET, F_FORCE_PTM, /* REG 7 */
F_MAX_FIELDS
@ -58,6 +77,9 @@ struct bq24257_init_data {
u8 ichg; /* charge current */
u8 vbat; /* regulation voltage */
u8 iterm; /* termination current */
u8 iilimit; /* input current limit */
u8 vovp; /* over voltage protection voltage */
u8 vindpm; /* VDMP input threshold voltage */
};
struct bq24257_state {
@ -71,6 +93,8 @@ struct bq24257_device {
struct device *dev;
struct power_supply *charger;
enum bq2425x_chip chip;
struct regmap *rmap;
struct regmap_field *rmap_fields[F_MAX_FIELDS];
@ -82,6 +106,8 @@ struct bq24257_device {
struct bq24257_state state;
struct mutex lock; /* protect state data */
bool iilimit_autoset_enable;
};
static bool bq24257_is_volatile_reg(struct device *dev, unsigned int reg)
@ -135,6 +161,7 @@ static const struct reg_field bq24257_reg_fields[] = {
[F_X2_TMR_EN] = REG_FIELD(BQ24257_REG_6, 7, 7),
[F_TMR] = REG_FIELD(BQ24257_REG_6, 5, 6),
[F_SYSOFF] = REG_FIELD(BQ24257_REG_6, 4, 4),
[F_TS_EN] = REG_FIELD(BQ24257_REG_6, 3, 3),
[F_TS_STAT] = REG_FIELD(BQ24257_REG_6, 0, 2),
/* REG 7 */
[F_VOVP] = REG_FIELD(BQ24257_REG_7, 5, 7),
@ -169,6 +196,26 @@ static const u32 bq24257_iterm_map[] = {
#define BQ24257_ITERM_MAP_SIZE ARRAY_SIZE(bq24257_iterm_map)
static const u32 bq24257_iilimit_map[] = {
100000, 150000, 500000, 900000, 1500000, 2000000
};
#define BQ24257_IILIMIT_MAP_SIZE ARRAY_SIZE(bq24257_iilimit_map)
static const u32 bq24257_vovp_map[] = {
6000000, 6500000, 7000000, 8000000, 9000000, 9500000, 10000000,
10500000
};
#define BQ24257_VOVP_MAP_SIZE ARRAY_SIZE(bq24257_vovp_map)
static const u32 bq24257_vindpm_map[] = {
4200000, 4280000, 4360000, 4440000, 4520000, 4600000, 4680000,
4760000
};
#define BQ24257_VINDPM_MAP_SIZE ARRAY_SIZE(bq24257_vindpm_map)
static int bq24257_field_read(struct bq24257_device *bq,
enum bq24257_fields field_id)
{
@ -220,6 +267,47 @@ enum bq24257_fault {
FAULT_INPUT_LDO_LOW,
};
static int bq24257_get_input_current_limit(struct bq24257_device *bq,
union power_supply_propval *val)
{
int ret;
ret = bq24257_field_read(bq, F_IILIMIT);
if (ret < 0)
return ret;
/*
* The "External ILIM" and "Production & Test" modes are not exposed
* through this driver and not being covered by the lookup table.
* Should such a mode have become active let's return an error rather
* than exceeding the bounds of the lookup table and returning
* garbage.
*/
if (ret >= BQ24257_IILIMIT_MAP_SIZE)
return -ENODATA;
val->intval = bq24257_iilimit_map[ret];
return 0;
}
static int bq24257_set_input_current_limit(struct bq24257_device *bq,
const union power_supply_propval *val)
{
/*
* Address the case where the user manually sets an input current limit
* while the charger auto-detection mechanism is is active. In this
* case we want to abort and go straight to the user-specified value.
*/
if (bq->iilimit_autoset_enable)
cancel_delayed_work_sync(&bq->iilimit_setup_work);
return bq24257_field_write(bq, F_IILIMIT,
bq24257_find_idx(val->intval,
bq24257_iilimit_map,
BQ24257_IILIMIT_MAP_SIZE));
}
static int bq24257_power_supply_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
@ -249,6 +337,10 @@ static int bq24257_power_supply_get_property(struct power_supply *psy,
val->strval = BQ24257_MANUFACTURER;
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = bq2425x_chip_name[bq->chip];
break;
case POWER_SUPPLY_PROP_ONLINE:
val->intval = state.power_good;
break;
@ -300,6 +392,9 @@ static int bq24257_power_supply_get_property(struct power_supply *psy,
val->intval = bq24257_iterm_map[bq->init_data.iterm];
break;
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
return bq24257_get_input_current_limit(bq, val);
default:
return -EINVAL;
}
@ -307,6 +402,31 @@ static int bq24257_power_supply_get_property(struct power_supply *psy,
return 0;
}
static int bq24257_power_supply_set_property(struct power_supply *psy,
enum power_supply_property prop,
const union power_supply_propval *val)
{
struct bq24257_device *bq = power_supply_get_drvdata(psy);
switch (prop) {
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
return bq24257_set_input_current_limit(bq, val);
default:
return -EINVAL;
}
}
static int bq24257_power_supply_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
return true;
default:
return false;
}
}
static int bq24257_get_chip_state(struct bq24257_device *bq,
struct bq24257_state *state)
{
@ -324,7 +444,26 @@ static int bq24257_get_chip_state(struct bq24257_device *bq,
state->fault = ret;
state->power_good = !gpiod_get_value_cansleep(bq->pg);
if (bq->pg)
state->power_good = !gpiod_get_value_cansleep(bq->pg);
else
/*
* If we have a chip without a dedicated power-good GPIO or
* some other explicit bit that would provide this information
* assume the power is good if there is no supply related
* fault - and not good otherwise. There is a possibility for
* other errors to mask that power in fact is not good but this
* is probably the best we can do here.
*/
switch (state->fault) {
case FAULT_INPUT_OVP:
case FAULT_INPUT_UVLO:
case FAULT_INPUT_LDO_LOW:
state->power_good = false;
break;
default:
state->power_good = true;
}
return 0;
}
@ -361,6 +500,28 @@ enum bq24257_in_ilimit {
IILIMIT_NONE,
};
enum bq24257_vovp {
VOVP_6000,
VOVP_6500,
VOVP_7000,
VOVP_8000,
VOVP_9000,
VOVP_9500,
VOVP_10000,
VOVP_10500
};
enum bq24257_vindpm {
VINDPM_4200,
VINDPM_4280,
VINDPM_4360,
VINDPM_4440,
VINDPM_4520,
VINDPM_4600,
VINDPM_4680,
VINDPM_4760
};
enum bq24257_port_type {
PORT_TYPE_DCP, /* Dedicated Charging Port */
PORT_TYPE_CDP, /* Charging Downstream Port */
@ -449,41 +610,43 @@ static void bq24257_handle_state_change(struct bq24257_device *bq,
{
int ret;
struct bq24257_state old_state;
bool reset_iilimit = false;
bool config_iilimit = false;
mutex_lock(&bq->lock);
old_state = bq->state;
mutex_unlock(&bq->lock);
if (!new_state->power_good) { /* power removed */
cancel_delayed_work_sync(&bq->iilimit_setup_work);
/*
* Handle BQ2425x state changes observing whether the D+/D- based input
* current limit autoset functionality is enabled.
*/
if (!new_state->power_good) {
dev_dbg(bq->dev, "Power removed\n");
if (bq->iilimit_autoset_enable) {
cancel_delayed_work_sync(&bq->iilimit_setup_work);
/* activate D+/D- port detection algorithm */
ret = bq24257_field_write(bq, F_DPDM_EN, 1);
/* activate D+/D- port detection algorithm */
ret = bq24257_field_write(bq, F_DPDM_EN, 1);
if (ret < 0)
goto error;
}
/*
* When power is removed always return to the default input
* current limit as configured during probe.
*/
ret = bq24257_field_write(bq, F_IILIMIT, bq->init_data.iilimit);
if (ret < 0)
goto error;
} else if (!old_state.power_good) {
dev_dbg(bq->dev, "Power inserted\n");
reset_iilimit = true;
} else if (!old_state.power_good) { /* power inserted */
config_iilimit = true;
} else if (new_state->fault == FAULT_NO_BAT) { /* battery removed */
cancel_delayed_work_sync(&bq->iilimit_setup_work);
reset_iilimit = true;
} else if (old_state.fault == FAULT_NO_BAT) { /* battery connected */
config_iilimit = true;
} else if (new_state->fault == FAULT_TIMER) { /* safety timer expired */
dev_err(bq->dev, "Safety timer expired! Battery dead?\n");
}
if (reset_iilimit) {
ret = bq24257_field_write(bq, F_IILIMIT, IILIMIT_500);
if (ret < 0)
goto error;
} else if (config_iilimit) {
schedule_delayed_work(&bq->iilimit_setup_work,
if (bq->iilimit_autoset_enable)
/* configure input current limit */
schedule_delayed_work(&bq->iilimit_setup_work,
msecs_to_jiffies(BQ24257_ILIM_SET_DELAY));
} else if (new_state->fault == FAULT_NO_BAT) {
dev_warn(bq->dev, "Battery removed\n");
} else if (new_state->fault == FAULT_TIMER) {
dev_err(bq->dev, "Safety timer expired! Battery dead?\n");
}
return;
@ -531,7 +694,9 @@ static int bq24257_hw_init(struct bq24257_device *bq)
} init_data[] = {
{F_ICHG, bq->init_data.ichg},
{F_VBAT, bq->init_data.vbat},
{F_ITERM, bq->init_data.iterm}
{F_ITERM, bq->init_data.iterm},
{F_VOVP, bq->init_data.vovp},
{F_VINDPM, bq->init_data.vindpm},
};
/*
@ -558,7 +723,16 @@ static int bq24257_hw_init(struct bq24257_device *bq)
bq->state = state;
mutex_unlock(&bq->lock);
if (!state.power_good)
if (!bq->iilimit_autoset_enable) {
dev_dbg(bq->dev, "manually setting iilimit = %u\n",
bq->init_data.iilimit);
/* program fixed input current limit */
ret = bq24257_field_write(bq, F_IILIMIT,
bq->init_data.iilimit);
if (ret < 0)
return ret;
} else if (!state.power_good)
/* activate D+/D- detection algorithm */
ret = bq24257_field_write(bq, F_DPDM_EN, 1);
else if (state.fault != FAULT_NO_BAT)
@ -569,6 +743,7 @@ static int bq24257_hw_init(struct bq24257_device *bq)
static enum power_supply_property bq24257_power_supply_props[] = {
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_HEALTH,
@ -577,6 +752,7 @@ static enum power_supply_property bq24257_power_supply_props[] = {
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
};
static char *bq24257_charger_supplied_to[] = {
@ -589,6 +765,96 @@ static const struct power_supply_desc bq24257_power_supply_desc = {
.properties = bq24257_power_supply_props,
.num_properties = ARRAY_SIZE(bq24257_power_supply_props),
.get_property = bq24257_power_supply_get_property,
.set_property = bq24257_power_supply_set_property,
.property_is_writeable = bq24257_power_supply_property_is_writeable,
};
static ssize_t bq24257_show_ovp_voltage(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct power_supply *psy = dev_get_drvdata(dev);
struct bq24257_device *bq = power_supply_get_drvdata(psy);
return scnprintf(buf, PAGE_SIZE, "%u\n",
bq24257_vovp_map[bq->init_data.vovp]);
}
static ssize_t bq24257_show_in_dpm_voltage(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct power_supply *psy = dev_get_drvdata(dev);
struct bq24257_device *bq = power_supply_get_drvdata(psy);
return scnprintf(buf, PAGE_SIZE, "%u\n",
bq24257_vindpm_map[bq->init_data.vindpm]);
}
static ssize_t bq24257_sysfs_show_enable(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct power_supply *psy = dev_get_drvdata(dev);
struct bq24257_device *bq = power_supply_get_drvdata(psy);
int ret;
if (strcmp(attr->attr.name, "high_impedance_enable") == 0)
ret = bq24257_field_read(bq, F_HZ_MODE);
else if (strcmp(attr->attr.name, "sysoff_enable") == 0)
ret = bq24257_field_read(bq, F_SYSOFF);
else
return -EINVAL;
if (ret < 0)
return ret;
return scnprintf(buf, PAGE_SIZE, "%d\n", ret);
}
static ssize_t bq24257_sysfs_set_enable(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct power_supply *psy = dev_get_drvdata(dev);
struct bq24257_device *bq = power_supply_get_drvdata(psy);
long val;
int ret;
if (kstrtol(buf, 10, &val) < 0)
return -EINVAL;
if (strcmp(attr->attr.name, "high_impedance_enable") == 0)
ret = bq24257_field_write(bq, F_HZ_MODE, (bool)val);
else if (strcmp(attr->attr.name, "sysoff_enable") == 0)
ret = bq24257_field_write(bq, F_SYSOFF, (bool)val);
else
return -EINVAL;
if (ret < 0)
return ret;
return count;
}
static DEVICE_ATTR(ovp_voltage, S_IRUGO, bq24257_show_ovp_voltage, NULL);
static DEVICE_ATTR(in_dpm_voltage, S_IRUGO, bq24257_show_in_dpm_voltage, NULL);
static DEVICE_ATTR(high_impedance_enable, S_IWUSR | S_IRUGO,
bq24257_sysfs_show_enable, bq24257_sysfs_set_enable);
static DEVICE_ATTR(sysoff_enable, S_IWUSR | S_IRUGO,
bq24257_sysfs_show_enable, bq24257_sysfs_set_enable);
static struct attribute *bq24257_charger_attr[] = {
&dev_attr_ovp_voltage.attr,
&dev_attr_in_dpm_voltage.attr,
&dev_attr_high_impedance_enable.attr,
&dev_attr_sysoff_enable.attr,
NULL,
};
static const struct attribute_group bq24257_attr_group = {
.attrs = bq24257_charger_attr,
};
static int bq24257_power_supply_init(struct bq24257_device *bq)
@ -598,36 +864,28 @@ static int bq24257_power_supply_init(struct bq24257_device *bq)
psy_cfg.supplied_to = bq24257_charger_supplied_to;
psy_cfg.num_supplicants = ARRAY_SIZE(bq24257_charger_supplied_to);
bq->charger = power_supply_register(bq->dev, &bq24257_power_supply_desc,
&psy_cfg);
if (IS_ERR(bq->charger))
return PTR_ERR(bq->charger);
bq->charger = devm_power_supply_register(bq->dev,
&bq24257_power_supply_desc,
&psy_cfg);
return 0;
return PTR_ERR_OR_ZERO(bq->charger);
}
static int bq24257_irq_probe(struct bq24257_device *bq)
static void bq24257_pg_gpio_probe(struct bq24257_device *bq)
{
struct gpio_desc *stat_irq;
bq->pg = devm_gpiod_get_optional(bq->dev, BQ24257_PG_GPIO, GPIOD_IN);
stat_irq = devm_gpiod_get_index(bq->dev, BQ24257_STAT_IRQ, 0, GPIOD_IN);
if (IS_ERR(stat_irq)) {
dev_err(bq->dev, "could not probe stat_irq pin\n");
return PTR_ERR(stat_irq);
if (PTR_ERR(bq->pg) == -EPROBE_DEFER) {
dev_info(bq->dev, "probe retry requested for PG pin\n");
return;
} else if (IS_ERR(bq->pg)) {
dev_err(bq->dev, "error probing PG pin\n");
bq->pg = NULL;
return;
}
return gpiod_to_irq(stat_irq);
}
static int bq24257_pg_gpio_probe(struct bq24257_device *bq)
{
bq->pg = devm_gpiod_get_index(bq->dev, BQ24257_PG_GPIO, 0, GPIOD_IN);
if (IS_ERR(bq->pg)) {
dev_err(bq->dev, "could not probe PG pin\n");
return PTR_ERR(bq->pg);
}
return 0;
if (bq->pg)
dev_dbg(bq->dev, "probed PG pin = %d\n", desc_to_gpio(bq->pg));
}
static int bq24257_fw_probe(struct bq24257_device *bq)
@ -635,6 +893,7 @@ static int bq24257_fw_probe(struct bq24257_device *bq)
int ret;
u32 property;
/* Required properties */
ret = device_property_read_u32(bq->dev, "ti,charge-current", &property);
if (ret < 0)
return ret;
@ -658,6 +917,43 @@ static int bq24257_fw_probe(struct bq24257_device *bq)
bq->init_data.iterm = bq24257_find_idx(property, bq24257_iterm_map,
BQ24257_ITERM_MAP_SIZE);
/* Optional properties. If not provided use reasonable default. */
ret = device_property_read_u32(bq->dev, "ti,current-limit",
&property);
if (ret < 0) {
bq->iilimit_autoset_enable = true;
/*
* Explicitly set a default value which will be needed for
* devices that don't support the automatic setting of the input
* current limit through the charger type detection mechanism.
*/
bq->init_data.iilimit = IILIMIT_500;
} else
bq->init_data.iilimit =
bq24257_find_idx(property,
bq24257_iilimit_map,
BQ24257_IILIMIT_MAP_SIZE);
ret = device_property_read_u32(bq->dev, "ti,ovp-voltage",
&property);
if (ret < 0)
bq->init_data.vovp = VOVP_6500;
else
bq->init_data.vovp = bq24257_find_idx(property,
bq24257_vovp_map,
BQ24257_VOVP_MAP_SIZE);
ret = device_property_read_u32(bq->dev, "ti,in-dpm-voltage",
&property);
if (ret < 0)
bq->init_data.vindpm = VINDPM_4360;
else
bq->init_data.vindpm =
bq24257_find_idx(property,
bq24257_vindpm_map,
BQ24257_VINDPM_MAP_SIZE);
return 0;
}
@ -666,6 +962,7 @@ static int bq24257_probe(struct i2c_client *client,
{
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
struct device *dev = &client->dev;
const struct acpi_device_id *acpi_id;
struct bq24257_device *bq;
int ret;
int i;
@ -682,6 +979,18 @@ static int bq24257_probe(struct i2c_client *client,
bq->client = client;
bq->dev = dev;
if (ACPI_HANDLE(dev)) {
acpi_id = acpi_match_device(dev->driver->acpi_match_table,
&client->dev);
if (!acpi_id) {
dev_err(dev, "Failed to match ACPI device\n");
return -ENODEV;
}
bq->chip = (enum bq2425x_chip)acpi_id->driver_data;
} else {
bq->chip = (enum bq2425x_chip)id->driver_data;
}
mutex_init(&bq->lock);
bq->rmap = devm_regmap_init_i2c(client, &bq24257_regmap_config);
@ -703,8 +1012,6 @@ static int bq24257_probe(struct i2c_client *client,
i2c_set_clientdata(client, bq);
INIT_DELAYED_WORK(&bq->iilimit_setup_work, bq24257_iilimit_setup_work);
if (!dev->platform_data) {
ret = bq24257_fw_probe(bq);
if (ret < 0) {
@ -715,10 +1022,31 @@ static int bq24257_probe(struct i2c_client *client,
return -ENODEV;
}
/* we can only check Power Good status by probing the PG pin */
ret = bq24257_pg_gpio_probe(bq);
if (ret < 0)
return ret;
/*
* The BQ24250 doesn't support the D+/D- based charger type detection
* used for the automatic setting of the input current limit setting so
* explicitly disable that feature.
*/
if (bq->chip == BQ24250)
bq->iilimit_autoset_enable = false;
if (bq->iilimit_autoset_enable)
INIT_DELAYED_WORK(&bq->iilimit_setup_work,
bq24257_iilimit_setup_work);
/*
* The BQ24250 doesn't have a dedicated Power Good (PG) pin so let's
* not probe for it and instead use a SW-based approach to determine
* the PG state. We also use a SW-based approach for all other devices
* if the PG pin is either not defined or can't be probed.
*/
if (bq->chip != BQ24250)
bq24257_pg_gpio_probe(bq);
if (PTR_ERR(bq->pg) == -EPROBE_DEFER)
return PTR_ERR(bq->pg);
else if (!bq->pg)
dev_info(bq->dev, "using SW-based power-good detection\n");
/* reset all registers to defaults */
ret = bq24257_field_write(bq, F_RESET, 1);
@ -740,36 +1068,39 @@ static int bq24257_probe(struct i2c_client *client,
return ret;
}
if (client->irq <= 0)
client->irq = bq24257_irq_probe(bq);
if (client->irq < 0) {
dev_err(dev, "no irq resource found\n");
return client->irq;
}
ret = devm_request_threaded_irq(dev, client->irq, NULL,
bq24257_irq_handler_thread,
IRQF_TRIGGER_FALLING |
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
BQ24257_STAT_IRQ, bq);
if (ret)
bq2425x_chip_name[bq->chip], bq);
if (ret) {
dev_err(dev, "Failed to request IRQ #%d\n", client->irq);
return ret;
}
ret = bq24257_power_supply_init(bq);
if (ret < 0)
if (ret < 0) {
dev_err(dev, "Failed to register power supply\n");
return ret;
}
return ret;
ret = sysfs_create_group(&bq->charger->dev.kobj, &bq24257_attr_group);
if (ret < 0) {
dev_err(dev, "Can't create sysfs entries\n");
return ret;
}
return 0;
}
static int bq24257_remove(struct i2c_client *client)
{
struct bq24257_device *bq = i2c_get_clientdata(client);
cancel_delayed_work_sync(&bq->iilimit_setup_work);
if (bq->iilimit_autoset_enable)
cancel_delayed_work_sync(&bq->iilimit_setup_work);
power_supply_unregister(bq->charger);
sysfs_remove_group(&bq->charger->dev.kobj, &bq24257_attr_group);
bq24257_field_write(bq, F_RESET, 1); /* reset to defaults */
@ -782,7 +1113,8 @@ static int bq24257_suspend(struct device *dev)
struct bq24257_device *bq = dev_get_drvdata(dev);
int ret = 0;
cancel_delayed_work_sync(&bq->iilimit_setup_work);
if (bq->iilimit_autoset_enable)
cancel_delayed_work_sync(&bq->iilimit_setup_work);
/* reset all registers to default (and activate standalone mode) */
ret = bq24257_field_write(bq, F_RESET, 1);
@ -823,19 +1155,25 @@ static const struct dev_pm_ops bq24257_pm = {
};
static const struct i2c_device_id bq24257_i2c_ids[] = {
{ "bq24257", 0 },
{ "bq24250", BQ24250 },
{ "bq24251", BQ24251 },
{ "bq24257", BQ24257 },
{},
};
MODULE_DEVICE_TABLE(i2c, bq24257_i2c_ids);
static const struct of_device_id bq24257_of_match[] = {
{ .compatible = "ti,bq24250", },
{ .compatible = "ti,bq24251", },
{ .compatible = "ti,bq24257", },
{ },
};
MODULE_DEVICE_TABLE(of, bq24257_of_match);
static const struct acpi_device_id bq24257_acpi_match[] = {
{"BQ242570", 0},
{ "BQ242500", BQ24250 },
{ "BQ242510", BQ24251 },
{ "BQ242570", BQ24257 },
{},
};
MODULE_DEVICE_TABLE(acpi, bq24257_acpi_match);

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1581,8 +1581,10 @@ static struct charger_desc *of_cm_parse_desc(struct device *dev)
cables = devm_kzalloc(dev, sizeof(*cables)
* chg_regs->num_cables,
GFP_KERNEL);
if (!cables)
if (!cables) {
of_node_put(child);
return ERR_PTR(-ENOMEM);
}
chg_regs->cables = cables;

View file

@ -508,23 +508,23 @@ out:
return param;
}
static int lp8727_parse_dt(struct device *dev)
static struct lp8727_platform_data *lp8727_parse_dt(struct device *dev)
{
struct device_node *np = dev->of_node;
struct device_node *child;
struct lp8727_platform_data *pdata;
const char *type;
/* If charging parameter is not defined, just skip parsing the dt */
if (of_get_child_count(np) == 0)
goto out;
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
return ERR_PTR(-ENOMEM);
of_property_read_u32(np, "debounce-ms", &pdata->debounce_msec);
/* If charging parameter is not defined, just skip parsing the dt */
if (of_get_child_count(np) == 0)
return pdata;
for_each_child_of_node(np, child) {
of_property_read_string(child, "charger-type", &type);
@ -535,29 +535,30 @@ static int lp8727_parse_dt(struct device *dev)
pdata->usb = lp8727_parse_charge_pdata(dev, child);
}
dev->platform_data = pdata;
out:
return 0;
return pdata;
}
#else
static int lp8727_parse_dt(struct device *dev)
static struct lp8727_platform_data *lp8727_parse_dt(struct device *dev)
{
return 0;
return NULL;
}
#endif
static int lp8727_probe(struct i2c_client *cl, const struct i2c_device_id *id)
{
struct lp8727_chg *pchg;
struct lp8727_platform_data *pdata;
int ret;
if (!i2c_check_functionality(cl->adapter, I2C_FUNC_SMBUS_I2C_BLOCK))
return -EIO;
if (cl->dev.of_node) {
ret = lp8727_parse_dt(&cl->dev);
if (ret)
return ret;
pdata = lp8727_parse_dt(&cl->dev);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
} else {
pdata = dev_get_platdata(&cl->dev);
}
pchg = devm_kzalloc(&cl->dev, sizeof(*pchg), GFP_KERNEL);
@ -566,7 +567,7 @@ static int lp8727_probe(struct i2c_client *cl, const struct i2c_device_id *id)
pchg->client = cl;
pchg->dev = &cl->dev;
pchg->pdata = cl->dev.platform_data;
pchg->pdata = pdata;
i2c_set_clientdata(cl, pchg);
mutex_init(&pchg->xfer_lock);

View file

@ -909,18 +909,21 @@ static int max17042_probe(struct i2c_client *client,
regmap_write(chip->regmap, MAX17042_LearnCFG, 0x0007);
}
chip->battery = power_supply_register(&client->dev, max17042_desc,
&psy_cfg);
chip->battery = devm_power_supply_register(&client->dev, max17042_desc,
&psy_cfg);
if (IS_ERR(chip->battery)) {
dev_err(&client->dev, "failed: power supply register\n");
return PTR_ERR(chip->battery);
}
if (client->irq) {
ret = request_threaded_irq(client->irq, NULL,
max17042_thread_handler,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
chip->battery->desc->name, chip);
ret = devm_request_threaded_irq(&client->dev, client->irq,
NULL,
max17042_thread_handler,
IRQF_TRIGGER_FALLING |
IRQF_ONESHOT,
chip->battery->desc->name,
chip);
if (!ret) {
regmap_update_bits(chip->regmap, MAX17042_CONFIG,
CONFIG_ALRT_BIT_ENBL,
@ -944,16 +947,6 @@ static int max17042_probe(struct i2c_client *client,
return 0;
}
static int max17042_remove(struct i2c_client *client)
{
struct max17042_chip *chip = i2c_get_clientdata(client);
if (client->irq)
free_irq(client->irq, chip);
power_supply_unregister(chip->battery);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int max17042_suspend(struct device *dev)
{
@ -1014,7 +1007,6 @@ static struct i2c_driver max17042_i2c_driver = {
.pm = &max17042_pm_ops,
},
.probe = max17042_probe,
.remove = max17042_remove,
.id_table = max17042_id,
};
module_i2c_driver(max17042_i2c_driver);

View file

@ -201,8 +201,7 @@ static int max8903_probe(struct platform_device *pdev)
if (pdata->dc_valid == false && pdata->usb_valid == false) {
dev_err(dev, "No valid power sources.\n");
ret = -EINVAL;
goto err;
return -EINVAL;
}
if (pdata->dc_valid) {
@ -216,8 +215,7 @@ static int max8903_probe(struct platform_device *pdev)
} else {
dev_err(dev, "When DC is wired, DOK and DCM should"
" be wired as well.\n");
ret = -EINVAL;
goto err;
return -EINVAL;
}
} else {
if (pdata->dcm) {
@ -225,8 +223,7 @@ static int max8903_probe(struct platform_device *pdev)
gpio_set_value(pdata->dcm, 0);
else {
dev_err(dev, "Invalid pin: dcm.\n");
ret = -EINVAL;
goto err;
return -EINVAL;
}
}
}
@ -238,8 +235,7 @@ static int max8903_probe(struct platform_device *pdev)
} else {
dev_err(dev, "When USB is wired, UOK should be wired."
"as well.\n");
ret = -EINVAL;
goto err;
return -EINVAL;
}
}
@ -248,32 +244,28 @@ static int max8903_probe(struct platform_device *pdev)
gpio_set_value(pdata->cen, (ta_in || usb_in) ? 0 : 1);
} else {
dev_err(dev, "Invalid pin: cen.\n");
ret = -EINVAL;
goto err;
return -EINVAL;
}
}
if (pdata->chg) {
if (!gpio_is_valid(pdata->chg)) {
dev_err(dev, "Invalid pin: chg.\n");
ret = -EINVAL;
goto err;
return -EINVAL;
}
}
if (pdata->flt) {
if (!gpio_is_valid(pdata->flt)) {
dev_err(dev, "Invalid pin: flt.\n");
ret = -EINVAL;
goto err;
return -EINVAL;
}
}
if (pdata->usus) {
if (!gpio_is_valid(pdata->usus)) {
dev_err(dev, "Invalid pin: usus.\n");
ret = -EINVAL;
goto err;
return -EINVAL;
}
}
@ -291,85 +283,56 @@ static int max8903_probe(struct platform_device *pdev)
psy_cfg.drv_data = data;
data->psy = power_supply_register(dev, &data->psy_desc, &psy_cfg);
data->psy = devm_power_supply_register(dev, &data->psy_desc, &psy_cfg);
if (IS_ERR(data->psy)) {
dev_err(dev, "failed: power supply register.\n");
ret = PTR_ERR(data->psy);
goto err;
return PTR_ERR(data->psy);
}
if (pdata->dc_valid) {
ret = request_threaded_irq(gpio_to_irq(pdata->dok),
NULL, max8903_dcin,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
"MAX8903 DC IN", data);
ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->dok),
NULL, max8903_dcin,
IRQF_TRIGGER_FALLING |
IRQF_TRIGGER_RISING,
"MAX8903 DC IN", data);
if (ret) {
dev_err(dev, "Cannot request irq %d for DC (%d)\n",
gpio_to_irq(pdata->dok), ret);
goto err_psy;
return ret;
}
}
if (pdata->usb_valid) {
ret = request_threaded_irq(gpio_to_irq(pdata->uok),
NULL, max8903_usbin,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
"MAX8903 USB IN", data);
ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->uok),
NULL, max8903_usbin,
IRQF_TRIGGER_FALLING |
IRQF_TRIGGER_RISING,
"MAX8903 USB IN", data);
if (ret) {
dev_err(dev, "Cannot request irq %d for USB (%d)\n",
gpio_to_irq(pdata->uok), ret);
goto err_dc_irq;
return ret;
}
}
if (pdata->flt) {
ret = request_threaded_irq(gpio_to_irq(pdata->flt),
NULL, max8903_fault,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
"MAX8903 Fault", data);
ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->flt),
NULL, max8903_fault,
IRQF_TRIGGER_FALLING |
IRQF_TRIGGER_RISING,
"MAX8903 Fault", data);
if (ret) {
dev_err(dev, "Cannot request irq %d for Fault (%d)\n",
gpio_to_irq(pdata->flt), ret);
goto err_usb_irq;
return ret;
}
}
return 0;
err_usb_irq:
if (pdata->usb_valid)
free_irq(gpio_to_irq(pdata->uok), data);
err_dc_irq:
if (pdata->dc_valid)
free_irq(gpio_to_irq(pdata->dok), data);
err_psy:
power_supply_unregister(data->psy);
err:
return ret;
}
static int max8903_remove(struct platform_device *pdev)
{
struct max8903_data *data = platform_get_drvdata(pdev);
if (data) {
struct max8903_pdata *pdata = &data->pdata;
if (pdata->flt)
free_irq(gpio_to_irq(pdata->flt), data);
if (pdata->usb_valid)
free_irq(gpio_to_irq(pdata->uok), data);
if (pdata->dc_valid)
free_irq(gpio_to_irq(pdata->dok), data);
power_supply_unregister(data->psy);
}
return 0;
}
static struct platform_driver max8903_driver = {
.probe = max8903_probe,
.remove = max8903_remove,
.driver = {
.name = "max8903-charger",
},

View file

@ -117,8 +117,7 @@ static int max8998_battery_probe(struct platform_device *pdev)
"EOC value not set: leave it unchanged.\n");
} else {
dev_err(max8998->dev, "Invalid EOC value\n");
ret = -EINVAL;
goto err;
return -EINVAL;
}
/* Setup Charge Restart Level */
@ -141,8 +140,7 @@ static int max8998_battery_probe(struct platform_device *pdev)
break;
default:
dev_err(max8998->dev, "Invalid Restart Level\n");
ret = -EINVAL;
goto err;
return -EINVAL;
}
/* Setup Charge Full Timeout */
@ -165,33 +163,21 @@ static int max8998_battery_probe(struct platform_device *pdev)
break;
default:
dev_err(max8998->dev, "Invalid Full Timeout value\n");
ret = -EINVAL;
goto err;
return -EINVAL;
}
psy_cfg.drv_data = max8998;
max8998->battery = power_supply_register(max8998->dev,
&max8998_battery_desc,
&psy_cfg);
max8998->battery = devm_power_supply_register(max8998->dev,
&max8998_battery_desc,
&psy_cfg);
if (IS_ERR(max8998->battery)) {
ret = PTR_ERR(max8998->battery);
dev_err(max8998->dev, "failed: power supply register: %d\n",
ret);
goto err;
return ret;
}
return 0;
err:
return ret;
}
static int max8998_battery_remove(struct platform_device *pdev)
{
struct max8998_battery_data *max8998 = platform_get_drvdata(pdev);
power_supply_unregister(max8998->battery);
return 0;
}
@ -205,7 +191,6 @@ static struct platform_driver max8998_battery_driver = {
.name = "max8998-battery",
},
.probe = max8998_battery_probe,
.remove = max8998_battery_remove,
.id_table = max8998_battery_id,
};

View file

@ -1264,5 +1264,4 @@ module_exit(pm2xxx_charger_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Rajkumar kasirajan, Olivier Launay");
MODULE_ALIAS("i2c:pm2xxx-charger");
MODULE_DESCRIPTION("PM2xxx charger management driver");

951
drivers/power/qcom_smbb.c Normal file
View file

@ -0,0 +1,951 @@
/* Copyright (c) 2014, Sony Mobile Communications Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This driver is for the multi-block Switch-Mode Battery Charger and Boost
* (SMBB) hardware, found in Qualcomm PM8941 PMICs. The charger is an
* integrated, single-cell lithium-ion battery charger.
*
* Sub-components:
* - Charger core
* - Buck
* - DC charge-path
* - USB charge-path
* - Battery interface
* - Boost (not implemented)
* - Misc
* - HF-Buck
*/
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#define SMBB_CHG_VMAX 0x040
#define SMBB_CHG_VSAFE 0x041
#define SMBB_CHG_CFG 0x043
#define SMBB_CHG_IMAX 0x044
#define SMBB_CHG_ISAFE 0x045
#define SMBB_CHG_VIN_MIN 0x047
#define SMBB_CHG_CTRL 0x049
#define CTRL_EN BIT(7)
#define SMBB_CHG_VBAT_WEAK 0x052
#define SMBB_CHG_IBAT_TERM_CHG 0x05b
#define IBAT_TERM_CHG_IEOC BIT(7)
#define IBAT_TERM_CHG_IEOC_BMS BIT(7)
#define IBAT_TERM_CHG_IEOC_CHG 0
#define SMBB_CHG_VBAT_DET 0x05d
#define SMBB_CHG_TCHG_MAX_EN 0x060
#define TCHG_MAX_EN BIT(7)
#define SMBB_CHG_WDOG_TIME 0x062
#define SMBB_CHG_WDOG_EN 0x065
#define WDOG_EN BIT(7)
#define SMBB_BUCK_REG_MODE 0x174
#define BUCK_REG_MODE BIT(0)
#define BUCK_REG_MODE_VBAT BIT(0)
#define BUCK_REG_MODE_VSYS 0
#define SMBB_BAT_PRES_STATUS 0x208
#define PRES_STATUS_BAT_PRES BIT(7)
#define SMBB_BAT_TEMP_STATUS 0x209
#define TEMP_STATUS_OK BIT(7)
#define TEMP_STATUS_HOT BIT(6)
#define SMBB_BAT_BTC_CTRL 0x249
#define BTC_CTRL_COMP_EN BIT(7)
#define BTC_CTRL_COLD_EXT BIT(1)
#define BTC_CTRL_HOT_EXT_N BIT(0)
#define SMBB_USB_IMAX 0x344
#define SMBB_USB_ENUM_TIMER_STOP 0x34e
#define ENUM_TIMER_STOP BIT(0)
#define SMBB_USB_SEC_ACCESS 0x3d0
#define SEC_ACCESS_MAGIC 0xa5
#define SMBB_USB_REV_BST 0x3ed
#define REV_BST_CHG_GONE BIT(7)
#define SMBB_DC_IMAX 0x444
#define SMBB_MISC_REV2 0x601
#define SMBB_MISC_BOOT_DONE 0x642
#define BOOT_DONE BIT(7)
#define STATUS_USBIN_VALID BIT(0) /* USB connection is valid */
#define STATUS_DCIN_VALID BIT(1) /* DC connection is valid */
#define STATUS_BAT_HOT BIT(2) /* Battery temp 1=Hot, 0=Cold */
#define STATUS_BAT_OK BIT(3) /* Battery temp OK */
#define STATUS_BAT_PRESENT BIT(4) /* Battery is present */
#define STATUS_CHG_DONE BIT(5) /* Charge cycle is complete */
#define STATUS_CHG_TRKL BIT(6) /* Trickle charging */
#define STATUS_CHG_FAST BIT(7) /* Fast charging */
#define STATUS_CHG_GONE BIT(8) /* No charger is connected */
enum smbb_attr {
ATTR_BAT_ISAFE,
ATTR_BAT_IMAX,
ATTR_USBIN_IMAX,
ATTR_DCIN_IMAX,
ATTR_BAT_VSAFE,
ATTR_BAT_VMAX,
ATTR_BAT_VMIN,
ATTR_CHG_VDET,
ATTR_VIN_MIN,
_ATTR_CNT,
};
struct smbb_charger {
unsigned int revision;
unsigned int addr;
struct device *dev;
bool dc_disabled;
bool jeita_ext_temp;
unsigned long status;
struct mutex statlock;
unsigned int attr[_ATTR_CNT];
struct power_supply *usb_psy;
struct power_supply *dc_psy;
struct power_supply *bat_psy;
struct regmap *regmap;
};
static int smbb_vbat_weak_fn(unsigned int index)
{
return 2100000 + index * 100000;
}
static int smbb_vin_fn(unsigned int index)
{
if (index > 42)
return 5600000 + (index - 43) * 200000;
return 3400000 + index * 50000;
}
static int smbb_vmax_fn(unsigned int index)
{
return 3240000 + index * 10000;
}
static int smbb_vbat_det_fn(unsigned int index)
{
return 3240000 + index * 20000;
}
static int smbb_imax_fn(unsigned int index)
{
if (index < 2)
return 100000 + index * 50000;
return index * 100000;
}
static int smbb_bat_imax_fn(unsigned int index)
{
return index * 50000;
}
static unsigned int smbb_hw_lookup(unsigned int val, int (*fn)(unsigned int))
{
unsigned int widx;
unsigned int sel;
for (widx = sel = 0; (*fn)(widx) <= val; ++widx)
sel = widx;
return sel;
}
static const struct smbb_charger_attr {
const char *name;
unsigned int reg;
unsigned int safe_reg;
unsigned int max;
unsigned int min;
unsigned int fail_ok;
int (*hw_fn)(unsigned int);
} smbb_charger_attrs[] = {
[ATTR_BAT_ISAFE] = {
.name = "qcom,fast-charge-safe-current",
.reg = SMBB_CHG_ISAFE,
.max = 3000000,
.min = 200000,
.hw_fn = smbb_bat_imax_fn,
.fail_ok = 1,
},
[ATTR_BAT_IMAX] = {
.name = "qcom,fast-charge-current-limit",
.reg = SMBB_CHG_IMAX,
.safe_reg = SMBB_CHG_ISAFE,
.max = 3000000,
.min = 200000,
.hw_fn = smbb_bat_imax_fn,
},
[ATTR_DCIN_IMAX] = {
.name = "qcom,dc-current-limit",
.reg = SMBB_DC_IMAX,
.max = 2500000,
.min = 100000,
.hw_fn = smbb_imax_fn,
},
[ATTR_BAT_VSAFE] = {
.name = "qcom,fast-charge-safe-voltage",
.reg = SMBB_CHG_VSAFE,
.max = 5000000,
.min = 3240000,
.hw_fn = smbb_vmax_fn,
.fail_ok = 1,
},
[ATTR_BAT_VMAX] = {
.name = "qcom,fast-charge-high-threshold-voltage",
.reg = SMBB_CHG_VMAX,
.safe_reg = SMBB_CHG_VSAFE,
.max = 5000000,
.min = 3240000,
.hw_fn = smbb_vmax_fn,
},
[ATTR_BAT_VMIN] = {
.name = "qcom,fast-charge-low-threshold-voltage",
.reg = SMBB_CHG_VBAT_WEAK,
.max = 3600000,
.min = 2100000,
.hw_fn = smbb_vbat_weak_fn,
},
[ATTR_CHG_VDET] = {
.name = "qcom,auto-recharge-threshold-voltage",
.reg = SMBB_CHG_VBAT_DET,
.max = 5000000,
.min = 3240000,
.hw_fn = smbb_vbat_det_fn,
},
[ATTR_VIN_MIN] = {
.name = "qcom,minimum-input-voltage",
.reg = SMBB_CHG_VIN_MIN,
.max = 9600000,
.min = 4200000,
.hw_fn = smbb_vin_fn,
},
[ATTR_USBIN_IMAX] = {
.name = "usb-charge-current-limit",
.reg = SMBB_USB_IMAX,
.max = 2500000,
.min = 100000,
.hw_fn = smbb_imax_fn,
},
};
static int smbb_charger_attr_write(struct smbb_charger *chg,
enum smbb_attr which, unsigned int val)
{
const struct smbb_charger_attr *prop;
unsigned int wval;
unsigned int out;
int rc;
prop = &smbb_charger_attrs[which];
if (val > prop->max || val < prop->min) {
dev_err(chg->dev, "value out of range for %s [%u:%u]\n",
prop->name, prop->min, prop->max);
return -EINVAL;
}
if (prop->safe_reg) {
rc = regmap_read(chg->regmap,
chg->addr + prop->safe_reg, &wval);
if (rc) {
dev_err(chg->dev,
"unable to read safe value for '%s'\n",
prop->name);
return rc;
}
wval = prop->hw_fn(wval);
if (val > wval) {
dev_warn(chg->dev,
"%s above safe value, clamping at %u\n",
prop->name, wval);
val = wval;
}
}
wval = smbb_hw_lookup(val, prop->hw_fn);
rc = regmap_write(chg->regmap, chg->addr + prop->reg, wval);
if (rc) {
dev_err(chg->dev, "unable to update %s", prop->name);
return rc;
}
out = prop->hw_fn(wval);
if (out != val) {
dev_warn(chg->dev,
"%s inaccurate, rounded to %u\n",
prop->name, out);
}
dev_dbg(chg->dev, "%s <= %d\n", prop->name, out);
chg->attr[which] = out;
return 0;
}
static int smbb_charger_attr_read(struct smbb_charger *chg,
enum smbb_attr which)
{
const struct smbb_charger_attr *prop;
unsigned int val;
int rc;
prop = &smbb_charger_attrs[which];
rc = regmap_read(chg->regmap, chg->addr + prop->reg, &val);
if (rc) {
dev_err(chg->dev, "failed to read %s\n", prop->name);
return rc;
}
val = prop->hw_fn(val);
dev_dbg(chg->dev, "%s => %d\n", prop->name, val);
chg->attr[which] = val;
return 0;
}
static int smbb_charger_attr_parse(struct smbb_charger *chg,
enum smbb_attr which)
{
const struct smbb_charger_attr *prop;
unsigned int val;
int rc;
prop = &smbb_charger_attrs[which];
rc = of_property_read_u32(chg->dev->of_node, prop->name, &val);
if (rc == 0) {
rc = smbb_charger_attr_write(chg, which, val);
if (!rc || !prop->fail_ok)
return rc;
}
return smbb_charger_attr_read(chg, which);
}
static void smbb_set_line_flag(struct smbb_charger *chg, int irq, int flag)
{
bool state;
int ret;
ret = irq_get_irqchip_state(irq, IRQCHIP_STATE_LINE_LEVEL, &state);
if (ret < 0) {
dev_err(chg->dev, "failed to read irq line\n");
return;
}
mutex_lock(&chg->statlock);
if (state)
chg->status |= flag;
else
chg->status &= ~flag;
mutex_unlock(&chg->statlock);
dev_dbg(chg->dev, "status = %03lx\n", chg->status);
}
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);
power_supply_changed(chg->usb_psy);
return IRQ_HANDLED;
}
static irqreturn_t smbb_dc_valid_handler(int irq, void *_data)
{
struct smbb_charger *chg = _data;
smbb_set_line_flag(chg, irq, STATUS_DCIN_VALID);
if (!chg->dc_disabled)
power_supply_changed(chg->dc_psy);
return IRQ_HANDLED;
}
static irqreturn_t smbb_bat_temp_handler(int irq, void *_data)
{
struct smbb_charger *chg = _data;
unsigned int val;
int rc;
rc = regmap_read(chg->regmap, chg->addr + SMBB_BAT_TEMP_STATUS, &val);
if (rc)
return IRQ_HANDLED;
mutex_lock(&chg->statlock);
if (val & TEMP_STATUS_OK) {
chg->status |= STATUS_BAT_OK;
} else {
chg->status &= ~STATUS_BAT_OK;
if (val & TEMP_STATUS_HOT)
chg->status |= STATUS_BAT_HOT;
}
mutex_unlock(&chg->statlock);
power_supply_changed(chg->bat_psy);
return IRQ_HANDLED;
}
static irqreturn_t smbb_bat_present_handler(int irq, void *_data)
{
struct smbb_charger *chg = _data;
smbb_set_line_flag(chg, irq, STATUS_BAT_PRESENT);
power_supply_changed(chg->bat_psy);
return IRQ_HANDLED;
}
static irqreturn_t smbb_chg_done_handler(int irq, void *_data)
{
struct smbb_charger *chg = _data;
smbb_set_line_flag(chg, irq, STATUS_CHG_DONE);
power_supply_changed(chg->bat_psy);
return IRQ_HANDLED;
}
static irqreturn_t smbb_chg_gone_handler(int irq, void *_data)
{
struct smbb_charger *chg = _data;
smbb_set_line_flag(chg, irq, STATUS_CHG_GONE);
power_supply_changed(chg->bat_psy);
power_supply_changed(chg->usb_psy);
if (!chg->dc_disabled)
power_supply_changed(chg->dc_psy);
return IRQ_HANDLED;
}
static irqreturn_t smbb_chg_fast_handler(int irq, void *_data)
{
struct smbb_charger *chg = _data;
smbb_set_line_flag(chg, irq, STATUS_CHG_FAST);
power_supply_changed(chg->bat_psy);
return IRQ_HANDLED;
}
static irqreturn_t smbb_chg_trkl_handler(int irq, void *_data)
{
struct smbb_charger *chg = _data;
smbb_set_line_flag(chg, irq, STATUS_CHG_TRKL);
power_supply_changed(chg->bat_psy);
return IRQ_HANDLED;
}
static const struct smbb_irq {
const char *name;
irqreturn_t (*handler)(int, void *);
} smbb_charger_irqs[] = {
{ "chg-done", smbb_chg_done_handler },
{ "chg-fast", smbb_chg_fast_handler },
{ "chg-trkl", smbb_chg_trkl_handler },
{ "bat-temp-ok", smbb_bat_temp_handler },
{ "bat-present", smbb_bat_present_handler },
{ "chg-gone", smbb_chg_gone_handler },
{ "usb-valid", smbb_usb_valid_handler },
{ "dc-valid", smbb_dc_valid_handler },
};
static int smbb_usbin_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct smbb_charger *chg = power_supply_get_drvdata(psy);
int rc = 0;
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
mutex_lock(&chg->statlock);
val->intval = !(chg->status & STATUS_CHG_GONE) &&
(chg->status & STATUS_USBIN_VALID);
mutex_unlock(&chg->statlock);
break;
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
val->intval = chg->attr[ATTR_USBIN_IMAX];
break;
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX:
val->intval = 2500000;
break;
default:
rc = -EINVAL;
break;
}
return rc;
}
static int smbb_usbin_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct smbb_charger *chg = power_supply_get_drvdata(psy);
int rc;
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
rc = smbb_charger_attr_write(chg, ATTR_USBIN_IMAX,
val->intval);
break;
default:
rc = -EINVAL;
break;
}
return rc;
}
static int smbb_dcin_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct smbb_charger *chg = power_supply_get_drvdata(psy);
int rc = 0;
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
mutex_lock(&chg->statlock);
val->intval = !(chg->status & STATUS_CHG_GONE) &&
(chg->status & STATUS_DCIN_VALID);
mutex_unlock(&chg->statlock);
break;
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
val->intval = chg->attr[ATTR_DCIN_IMAX];
break;
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX:
val->intval = 2500000;
break;
default:
rc = -EINVAL;
break;
}
return rc;
}
static int smbb_dcin_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct smbb_charger *chg = power_supply_get_drvdata(psy);
int rc;
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
rc = smbb_charger_attr_write(chg, ATTR_DCIN_IMAX,
val->intval);
break;
default:
rc = -EINVAL;
break;
}
return rc;
}
static int smbb_charger_writable_property(struct power_supply *psy,
enum power_supply_property psp)
{
return psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT;
}
static int smbb_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct smbb_charger *chg = power_supply_get_drvdata(psy);
unsigned long status;
int rc = 0;
mutex_lock(&chg->statlock);
status = chg->status;
mutex_unlock(&chg->statlock);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
if (status & STATUS_CHG_GONE)
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
else if (!(status & (STATUS_DCIN_VALID | STATUS_USBIN_VALID)))
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
else if (status & STATUS_CHG_DONE)
val->intval = POWER_SUPPLY_STATUS_FULL;
else if (!(status & STATUS_BAT_OK))
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
else if (status & (STATUS_CHG_FAST | STATUS_CHG_TRKL))
val->intval = POWER_SUPPLY_STATUS_CHARGING;
else /* everything is ok for charging, but we are not... */
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
break;
case POWER_SUPPLY_PROP_HEALTH:
if (status & STATUS_BAT_OK)
val->intval = POWER_SUPPLY_HEALTH_GOOD;
else if (status & STATUS_BAT_HOT)
val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
else
val->intval = POWER_SUPPLY_HEALTH_COLD;
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
if (status & STATUS_CHG_FAST)
val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
else if (status & STATUS_CHG_TRKL)
val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
else
val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = !!(status & STATUS_BAT_PRESENT);
break;
case POWER_SUPPLY_PROP_CURRENT_MAX:
val->intval = chg->attr[ATTR_BAT_IMAX];
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
val->intval = chg->attr[ATTR_BAT_VMAX];
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
/* this charger is a single-cell lithium-ion battery charger
* only. If you hook up some other technology, there will be
* fireworks.
*/
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
val->intval = 3000000; /* single-cell li-ion low end */
break;
default:
rc = -EINVAL;
break;
}
return rc;
}
static int smbb_battery_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct smbb_charger *chg = power_supply_get_drvdata(psy);
int rc;
switch (psp) {
case POWER_SUPPLY_PROP_CURRENT_MAX:
rc = smbb_charger_attr_write(chg, ATTR_BAT_IMAX, val->intval);
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
rc = smbb_charger_attr_write(chg, ATTR_BAT_VMAX, val->intval);
break;
default:
rc = -EINVAL;
break;
}
return rc;
}
static int smbb_battery_writable_property(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_CURRENT_MAX:
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
return 1;
default:
return 0;
}
}
static enum power_supply_property smbb_charger_properties[] = {
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
};
static enum power_supply_property smbb_battery_properties[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_CURRENT_MAX,
POWER_SUPPLY_PROP_VOLTAGE_MAX,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_TECHNOLOGY,
};
static const struct reg_off_mask_default {
unsigned int offset;
unsigned int mask;
unsigned int value;
unsigned int rev_mask;
} smbb_charger_setup[] = {
/* The bootloader is supposed to set this... make sure anyway. */
{ SMBB_MISC_BOOT_DONE, BOOT_DONE, BOOT_DONE },
/* Disable software timer */
{ SMBB_CHG_TCHG_MAX_EN, TCHG_MAX_EN, 0 },
/* Clear and disable watchdog */
{ SMBB_CHG_WDOG_TIME, 0xff, 160 },
{ SMBB_CHG_WDOG_EN, WDOG_EN, 0 },
/* Use charger based EoC detection */
{ SMBB_CHG_IBAT_TERM_CHG, IBAT_TERM_CHG_IEOC, IBAT_TERM_CHG_IEOC_CHG },
/* Disable GSM PA load adjustment.
* The PA signal is incorrectly connected on v2.
*/
{ SMBB_CHG_CFG, 0xff, 0x00, BIT(3) },
/* Use VBAT (not VSYS) to compensate for IR drop during fast charging */
{ SMBB_BUCK_REG_MODE, BUCK_REG_MODE, BUCK_REG_MODE_VBAT },
/* Enable battery temperature comparators */
{ SMBB_BAT_BTC_CTRL, BTC_CTRL_COMP_EN, BTC_CTRL_COMP_EN },
/* Stop USB enumeration timer */
{ SMBB_USB_ENUM_TIMER_STOP, ENUM_TIMER_STOP, ENUM_TIMER_STOP },
#if 0 /* FIXME supposedly only to disable hardware ARB termination */
{ SMBB_USB_SEC_ACCESS, SEC_ACCESS_MAGIC },
{ SMBB_USB_REV_BST, 0xff, REV_BST_CHG_GONE },
#endif
/* Stop USB enumeration timer, again */
{ SMBB_USB_ENUM_TIMER_STOP, ENUM_TIMER_STOP, ENUM_TIMER_STOP },
/* Enable charging */
{ SMBB_CHG_CTRL, CTRL_EN, CTRL_EN },
};
static char *smbb_bif[] = { "smbb-bif" };
static const struct power_supply_desc bat_psy_desc = {
.name = "smbb-bif",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = smbb_battery_properties,
.num_properties = ARRAY_SIZE(smbb_battery_properties),
.get_property = smbb_battery_get_property,
.set_property = smbb_battery_set_property,
.property_is_writeable = smbb_battery_writable_property,
};
static const struct power_supply_desc usb_psy_desc = {
.name = "smbb-usbin",
.type = POWER_SUPPLY_TYPE_USB,
.properties = smbb_charger_properties,
.num_properties = ARRAY_SIZE(smbb_charger_properties),
.get_property = smbb_usbin_get_property,
.set_property = smbb_usbin_set_property,
.property_is_writeable = smbb_charger_writable_property,
};
static const struct power_supply_desc dc_psy_desc = {
.name = "smbb-dcin",
.type = POWER_SUPPLY_TYPE_MAINS,
.properties = smbb_charger_properties,
.num_properties = ARRAY_SIZE(smbb_charger_properties),
.get_property = smbb_dcin_get_property,
.set_property = smbb_dcin_set_property,
.property_is_writeable = smbb_charger_writable_property,
};
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;
int rc, i;
chg = devm_kzalloc(&pdev->dev, sizeof(*chg), GFP_KERNEL);
if (!chg)
return -ENOMEM;
chg->dev = &pdev->dev;
mutex_init(&chg->statlock);
chg->regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!chg->regmap) {
dev_err(&pdev->dev, "failed to locate regmap\n");
return -ENODEV;
}
rc = of_property_read_u32(pdev->dev.of_node, "reg", &chg->addr);
if (rc) {
dev_err(&pdev->dev, "missing or invalid 'reg' property\n");
return rc;
}
rc = regmap_read(chg->regmap, chg->addr + SMBB_MISC_REV2, &chg->revision);
if (rc) {
dev_err(&pdev->dev, "unable to read revision\n");
return rc;
}
chg->revision += 1;
if (chg->revision != 2 && chg->revision != 3) {
dev_err(&pdev->dev, "v1 hardware not supported\n");
return -ENODEV;
}
dev_info(&pdev->dev, "Initializing SMBB rev %u", chg->revision);
chg->dc_disabled = of_property_read_bool(pdev->dev.of_node, "qcom,disable-dc");
for (i = 0; i < _ATTR_CNT; ++i) {
rc = smbb_charger_attr_parse(chg, i);
if (rc) {
dev_err(&pdev->dev, "failed to parse/apply settings\n");
return rc;
}
}
bat_cfg.drv_data = chg;
bat_cfg.of_node = pdev->dev.of_node;
chg->bat_psy = devm_power_supply_register(&pdev->dev,
&bat_psy_desc,
&bat_cfg);
if (IS_ERR(chg->bat_psy)) {
dev_err(&pdev->dev, "failed to register battery\n");
return PTR_ERR(chg->bat_psy);
}
usb_cfg.drv_data = chg;
usb_cfg.supplied_to = smbb_bif;
usb_cfg.num_supplicants = ARRAY_SIZE(smbb_bif);
chg->usb_psy = devm_power_supply_register(&pdev->dev,
&usb_psy_desc,
&usb_cfg);
if (IS_ERR(chg->usb_psy)) {
dev_err(&pdev->dev, "failed to register USB power supply\n");
return PTR_ERR(chg->usb_psy);
}
if (!chg->dc_disabled) {
dc_cfg.drv_data = chg;
dc_cfg.supplied_to = smbb_bif;
dc_cfg.num_supplicants = ARRAY_SIZE(smbb_bif);
chg->dc_psy = devm_power_supply_register(&pdev->dev,
&dc_psy_desc,
&dc_cfg);
if (IS_ERR(chg->dc_psy)) {
dev_err(&pdev->dev, "failed to register DC power supply\n");
return PTR_ERR(chg->dc_psy);
}
}
for (i = 0; i < ARRAY_SIZE(smbb_charger_irqs); ++i) {
int irq;
irq = platform_get_irq_byname(pdev, smbb_charger_irqs[i].name);
if (irq < 0) {
dev_err(&pdev->dev, "failed to get irq '%s'\n",
smbb_charger_irqs[i].name);
return irq;
}
smbb_charger_irqs[i].handler(irq, chg);
rc = devm_request_threaded_irq(&pdev->dev, irq, NULL,
smbb_charger_irqs[i].handler, IRQF_ONESHOT,
smbb_charger_irqs[i].name, chg);
if (rc) {
dev_err(&pdev->dev, "failed to request irq '%s'\n",
smbb_charger_irqs[i].name);
return rc;
}
}
chg->jeita_ext_temp = of_property_read_bool(pdev->dev.of_node,
"qcom,jeita-extended-temp-range");
/* Set temperature range to [35%:70%] or [25%:80%] accordingly */
rc = regmap_update_bits(chg->regmap, chg->addr + SMBB_BAT_BTC_CTRL,
BTC_CTRL_COLD_EXT | BTC_CTRL_HOT_EXT_N,
chg->jeita_ext_temp ?
BTC_CTRL_COLD_EXT :
BTC_CTRL_HOT_EXT_N);
if (rc) {
dev_err(&pdev->dev,
"unable to set %s temperature range\n",
chg->jeita_ext_temp ? "JEITA extended" : "normal");
return rc;
}
for (i = 0; i < ARRAY_SIZE(smbb_charger_setup); ++i) {
const struct reg_off_mask_default *r = &smbb_charger_setup[i];
if (r->rev_mask & BIT(chg->revision))
continue;
rc = regmap_update_bits(chg->regmap, chg->addr + r->offset,
r->mask, r->value);
if (rc) {
dev_err(&pdev->dev,
"unable to initializing charging, bailing\n");
return rc;
}
}
platform_set_drvdata(pdev, chg);
return 0;
}
static int smbb_charger_remove(struct platform_device *pdev)
{
struct smbb_charger *chg;
chg = platform_get_drvdata(pdev);
regmap_update_bits(chg->regmap, chg->addr + SMBB_CHG_CTRL, CTRL_EN, 0);
return 0;
}
static const struct of_device_id smbb_charger_id_table[] = {
{ .compatible = "qcom,pm8941-charger" },
{ }
};
MODULE_DEVICE_TABLE(of, smbb_charger_id_table);
static struct platform_driver smbb_charger_driver = {
.probe = smbb_charger_probe,
.remove = smbb_charger_remove,
.driver = {
.name = "qcom-smbb",
.of_match_table = smbb_charger_id_table,
},
};
module_platform_driver(smbb_charger_driver);
MODULE_DESCRIPTION("Qualcomm Switch-Mode Battery Charger and Boost driver");
MODULE_LICENSE("GPL v2");

View file

@ -15,7 +15,7 @@ config POWER_RESET_AS3722
This driver supports turning off board via a ams AS3722 power-off.
config POWER_RESET_AT91_POWEROFF
bool "Atmel AT91 poweroff driver"
tristate "Atmel AT91 poweroff driver"
depends on ARCH_AT91
default SOC_AT91SAM9 || SOC_SAMA5
help
@ -23,7 +23,7 @@ config POWER_RESET_AT91_POWEROFF
SoCs
config POWER_RESET_AT91_RESET
bool "Atmel AT91 reset driver"
tristate "Atmel AT91 reset driver"
depends on ARCH_AT91
default SOC_AT91SAM9 || SOC_SAMA5
help

View file

@ -10,6 +10,7 @@
* warranty of any kind, whether express or implied.
*/
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
@ -48,6 +49,7 @@ static const char *shdwc_wakeup_modes[] = {
};
static void __iomem *at91_shdwc_base;
static struct clk *sclk;
static void __init at91_wakeup_status(void)
{
@ -119,9 +121,10 @@ static void at91_poweroff_dt_set_wakeup_mode(struct platform_device *pdev)
writel(wakeup_mode | mode, at91_shdwc_base + AT91_SHDW_MR);
}
static int at91_poweroff_probe(struct platform_device *pdev)
static int __init at91_poweroff_probe(struct platform_device *pdev)
{
struct resource *res;
int ret;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
at91_shdwc_base = devm_ioremap_resource(&pdev->dev, res);
@ -130,6 +133,16 @@ static int at91_poweroff_probe(struct platform_device *pdev)
return PTR_ERR(at91_shdwc_base);
}
sclk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(sclk))
return PTR_ERR(sclk);
ret = clk_prepare_enable(sclk);
if (ret) {
dev_err(&pdev->dev, "Could not enable slow clock\n");
return ret;
}
at91_wakeup_status();
if (pdev->dev.of_node)
@ -140,6 +153,16 @@ static int at91_poweroff_probe(struct platform_device *pdev)
return 0;
}
static int __exit at91_poweroff_remove(struct platform_device *pdev)
{
if (pm_power_off == at91_poweroff)
pm_power_off = NULL;
clk_disable_unprepare(sclk);
return 0;
}
static const struct of_device_id at91_poweroff_of_match[] = {
{ .compatible = "atmel,at91sam9260-shdwc", },
{ .compatible = "atmel,at91sam9rl-shdwc", },
@ -148,10 +171,14 @@ static const struct of_device_id at91_poweroff_of_match[] = {
};
static struct platform_driver at91_poweroff_driver = {
.probe = at91_poweroff_probe,
.remove = __exit_p(at91_poweroff_remove),
.driver = {
.name = "at91-poweroff",
.of_match_table = at91_poweroff_of_match,
},
};
module_platform_driver(at91_poweroff_driver);
module_platform_driver_probe(at91_poweroff_driver, at91_poweroff_probe);
MODULE_AUTHOR("Atmel Corporation");
MODULE_DESCRIPTION("Shutdown driver for Atmel SoCs");
MODULE_LICENSE("GPL v2");

View file

@ -1,5 +1,5 @@
/*
* Atmel AT91 SAM9 SoCs reset code
* Atmel AT91 SAM9 & SAMA5 SoCs reset code
*
* Copyright (C) 2007 Atmel Corporation.
* Copyright (C) BitBox Ltd 2010
@ -11,6 +11,7 @@
* warranty of any kind, whether express or implied.
*/
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_address.h>
@ -46,6 +47,7 @@ enum reset_type {
};
static void __iomem *at91_ramc_base[2], *at91_rstc_base;
static struct clk *sclk;
/*
* unless the SDRAM is cleanly shutdown before we hit the
@ -178,11 +180,11 @@ static struct notifier_block at91_restart_nb = {
.priority = 192,
};
static int at91_reset_of_probe(struct platform_device *pdev)
static int __init at91_reset_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
struct device_node *np;
int idx = 0;
int ret, idx = 0;
at91_rstc_base = of_iomap(pdev->dev.of_node, 0);
if (!at91_rstc_base) {
@ -204,57 +206,36 @@ static int at91_reset_of_probe(struct platform_device *pdev)
match = of_match_node(at91_reset_of_match, pdev->dev.of_node);
at91_restart_nb.notifier_call = match->data;
return register_restart_handler(&at91_restart_nb);
}
static int at91_reset_platform_probe(struct platform_device *pdev)
{
const struct platform_device_id *match;
struct resource *res;
int idx = 0;
sclk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(sclk))
return PTR_ERR(sclk);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
at91_rstc_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(at91_rstc_base)) {
dev_err(&pdev->dev, "Could not map reset controller address\n");
return PTR_ERR(at91_rstc_base);
}
for (idx = 0; idx < 2; idx++) {
res = platform_get_resource(pdev, IORESOURCE_MEM, idx + 1 );
at91_ramc_base[idx] = devm_ioremap(&pdev->dev, res->start,
resource_size(res));
if (!at91_ramc_base[idx]) {
dev_err(&pdev->dev, "Could not map ram controller address\n");
return -ENOMEM;
}
}
match = platform_get_device_id(pdev);
at91_restart_nb.notifier_call =
(int (*)(struct notifier_block *,
unsigned long, void *)) match->driver_data;
return register_restart_handler(&at91_restart_nb);
}
static int at91_reset_probe(struct platform_device *pdev)
{
int ret;
if (pdev->dev.of_node)
ret = at91_reset_of_probe(pdev);
else
ret = at91_reset_platform_probe(pdev);
if (ret)
ret = clk_prepare_enable(sclk);
if (ret) {
dev_err(&pdev->dev, "Could not enable slow clock\n");
return ret;
}
ret = register_restart_handler(&at91_restart_nb);
if (ret) {
clk_disable_unprepare(sclk);
return ret;
}
at91_reset_status(pdev);
return 0;
}
static int __exit at91_reset_remove(struct platform_device *pdev)
{
unregister_restart_handler(&at91_restart_nb);
clk_disable_unprepare(sclk);
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 },
@ -262,11 +243,15 @@ static const struct platform_device_id at91_reset_plat_match[] = {
};
static struct platform_driver at91_reset_driver = {
.probe = at91_reset_probe,
.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(at91_reset_driver);
module_platform_driver_probe(at91_reset_driver, at91_reset_probe);
MODULE_AUTHOR("Atmel Corporation");
MODULE_DESCRIPTION("Reset driver for Atmel SoCs");
MODULE_LICENSE("GPL v2");

View file

@ -1760,5 +1760,4 @@ module_i2c_driver(rt9455_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anda-Maria Nicolae <[email protected]>");
MODULE_ALIAS("i2c:rt9455-charger");
MODULE_DESCRIPTION("Richtek RT9455 Charger Driver");

View file

@ -1332,4 +1332,3 @@ MODULE_AUTHOR("Bruce E. Robertson <[email protected]>");
MODULE_AUTHOR("Mika Westerberg <[email protected]>");
MODULE_DESCRIPTION("SMB347 battery charger driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("i2c:smb347");

View file

@ -353,6 +353,7 @@ static const struct of_device_id of_tps65090_charger_match[] = {
{ .compatible = "ti,tps65090-charger", },
{ /* end */ }
};
MODULE_DEVICE_TABLE(of, of_tps65090_charger_match);
static struct platform_driver tps65090_charger_driver = {
.driver = {

View file

@ -0,0 +1,264 @@
/*
* Battery charger driver for TI's tps65217
*
* Copyright (c) 2015, Collabora Ltd.
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
* This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
*/
/*
* Battery charger driver for TI's tps65217
*/
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/power_supply.h>
#include <linux/mfd/core.h>
#include <linux/mfd/tps65217.h>
#define POLL_INTERVAL (HZ * 2)
struct tps65217_charger {
struct tps65217 *tps;
struct device *dev;
struct power_supply *ac;
int ac_online;
int prev_ac_online;
struct task_struct *poll_task;
};
static enum power_supply_property tps65217_ac_props[] = {
POWER_SUPPLY_PROP_ONLINE,
};
static int tps65217_config_charger(struct tps65217_charger *charger)
{
int ret;
dev_dbg(charger->dev, "%s\n", __func__);
/*
* tps65217 rev. G, p. 31 (see p. 32 for NTC schematic)
*
* The device can be configured to support a 100k NTC (B = 3960) by
* setting the the NTC_TYPE bit in register CHGCONFIG1 to 1. However it
* is not recommended to do so. In sleep mode, the charger continues
* charging the battery, but all register values are reset to default
* values. Therefore, the charger would get the wrong temperature
* information. If 100k NTC setting is required, please contact the
* factory.
*
* ATTENTION, conflicting information, from p. 46
*
* NTC TYPE (for battery temperature measurement)
* 0 – 100k (curve 1, B = 3960)
* 1 – 10k (curve 2, B = 3480) (default on reset)
*
*/
ret = tps65217_clear_bits(charger->tps, TPS65217_REG_CHGCONFIG1,
TPS65217_CHGCONFIG1_NTC_TYPE,
TPS65217_PROTECT_NONE);
if (ret) {
dev_err(charger->dev,
"failed to set 100k NTC setting: %d\n", ret);
return ret;
}
return 0;
}
static int tps65217_enable_charging(struct tps65217_charger *charger)
{
int ret;
/* charger already enabled */
if (charger->ac_online)
return 0;
dev_dbg(charger->dev, "%s: enable charging\n", __func__);
ret = tps65217_set_bits(charger->tps, TPS65217_REG_CHGCONFIG1,
TPS65217_CHGCONFIG1_CHG_EN,
TPS65217_CHGCONFIG1_CHG_EN,
TPS65217_PROTECT_NONE);
if (ret) {
dev_err(charger->dev,
"%s: Error in writing CHG_EN in reg 0x%x: %d\n",
__func__, TPS65217_REG_CHGCONFIG1, ret);
return ret;
}
charger->ac_online = 1;
return 0;
}
static int tps65217_ac_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;
return 0;
}
return -EINVAL;
}
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;
ret = tps65217_reg_read(charger->tps, TPS65217_REG_STATUS, &val);
if (ret < 0) {
dev_err(charger->dev, "%s: Error in reading reg 0x%x\n",
__func__, TPS65217_REG_STATUS);
return IRQ_HANDLED;
}
dev_dbg(charger->dev, "%s: 0x%x\n", __func__, val);
/* check for AC status bit */
if (val & TPS65217_STATUS_ACPWR) {
ret = tps65217_enable_charging(charger);
if (ret) {
dev_err(charger->dev,
"failed to enable charger: %d\n", ret);
return IRQ_HANDLED;
}
} else {
charger->ac_online = 0;
}
if (charger->prev_ac_online != charger->ac_online)
power_supply_changed(charger->ac);
ret = tps65217_reg_read(charger->tps, TPS65217_REG_CHGCONFIG0, &val);
if (ret < 0) {
dev_err(charger->dev, "%s: Error in reading reg 0x%x\n",
__func__, TPS65217_REG_CHGCONFIG0);
return IRQ_HANDLED;
}
if (val & TPS65217_CHGCONFIG0_ACTIVE)
dev_dbg(charger->dev, "%s: charger is charging\n", __func__);
else
dev_dbg(charger->dev,
"%s: charger is NOT charging\n", __func__);
return IRQ_HANDLED;
}
static int tps65217_charger_poll_task(void *data)
{
set_freezable();
while (!kthread_should_stop()) {
schedule_timeout_interruptible(POLL_INTERVAL);
try_to_freeze();
tps65217_charger_irq(-1, data);
}
return 0;
}
static const struct power_supply_desc tps65217_charger_desc = {
.name = "tps65217-ac",
.type = POWER_SUPPLY_TYPE_MAINS,
.get_property = tps65217_ac_get_property,
.properties = tps65217_ac_props,
.num_properties = ARRAY_SIZE(tps65217_ac_props),
};
static int tps65217_charger_probe(struct platform_device *pdev)
{
struct tps65217 *tps = dev_get_drvdata(pdev->dev.parent);
struct tps65217_charger *charger;
int ret;
dev_dbg(&pdev->dev, "%s\n", __func__);
charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL);
if (!charger)
return -ENOMEM;
charger->tps = tps;
charger->dev = &pdev->dev;
charger->ac = devm_power_supply_register(&pdev->dev,
&tps65217_charger_desc,
NULL);
if (IS_ERR(charger->ac)) {
dev_err(&pdev->dev, "failed: power supply register\n");
return PTR_ERR(charger->ac);
}
ret = tps65217_config_charger(charger);
if (ret < 0) {
dev_err(charger->dev, "charger config failed, err %d\n", ret);
return ret;
}
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;
}
return 0;
}
static int tps65217_charger_remove(struct platform_device *pdev)
{
struct tps65217_charger *charger = platform_get_drvdata(pdev);
kthread_stop(charger->poll_task);
return 0;
}
static const struct of_device_id tps65217_charger_match_table[] = {
{ .compatible = "ti,tps65217-charger", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, tps65217_charger_match_table);
static struct platform_driver tps65217_charger_driver = {
.probe = tps65217_charger_probe,
.remove = tps65217_charger_remove,
.driver = {
.name = "tps65217-charger",
.of_match_table = of_match_ptr(tps65217_charger_match_table),
},
};
module_platform_driver(tps65217_charger_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Enric Balletbo Serra <[email protected]>");
MODULE_DESCRIPTION("TPS65217 battery charger driver");

View file

@ -22,7 +22,7 @@
#include <linux/power_supply.h>
#include <linux/notifier.h>
#include <linux/usb/otg.h>
#include <linux/i2c/twl4030-madc.h>
#include <linux/iio/consumer.h>
#define TWL4030_BCIMDEN 0x00
#define TWL4030_BCIMDKEY 0x01
@ -91,21 +91,23 @@
#define TWL4030_MSTATEC_COMPLETE1 0x0b
#define TWL4030_MSTATEC_COMPLETE4 0x0e
#if IS_REACHABLE(CONFIG_TWL4030_MADC)
/*
* If AC (Accessory Charger) voltage exceeds 4.5V (MADC 11)
* then AC is available.
*/
static inline int ac_available(void)
static inline int ac_available(struct iio_channel *channel_vac)
{
return twl4030_get_madc_conversion(11) > 4500;
int val, err;
if (!channel_vac)
return 0;
err = iio_read_channel_processed(channel_vac, &val);
if (err < 0)
return 0;
return val > 4500;
}
#else
static inline int ac_available(void)
{
return 0;
}
#endif
static bool allow_usb;
module_param(allow_usb, bool, 0644);
MODULE_PARM_DESC(allow_usb, "Allow USB charge drawing default current");
@ -128,6 +130,7 @@ struct twl4030_bci {
*/
unsigned int ichg_eoc, ichg_lo, ichg_hi;
unsigned int usb_cur, ac_cur;
struct iio_channel *channel_vac;
bool ac_is_active;
int usb_mode, ac_mode; /* charging mode requested */
#define CHARGE_OFF 0
@ -278,7 +281,7 @@ static int twl4030_charger_update_current(struct twl4030_bci *bci)
* If AC (Accessory Charger) voltage exceeds 4.5V (MADC 11)
* and AC is enabled, set current for 'ac'
*/
if (ac_available()) {
if (ac_available(bci->channel_vac)) {
cur = bci->ac_cur;
bci->ac_is_active = true;
} else {
@ -1048,6 +1051,12 @@ static int twl4030_bci_probe(struct platform_device *pdev)
return ret;
}
bci->channel_vac = iio_channel_get(&pdev->dev, "vac");
if (IS_ERR(bci->channel_vac)) {
bci->channel_vac = NULL;
dev_warn(&pdev->dev, "could not request vac iio channel");
}
INIT_WORK(&bci->work, twl4030_bci_usb_work);
INIT_DELAYED_WORK(&bci->current_worker, twl4030_current_worker);
@ -1069,7 +1078,7 @@ static int twl4030_bci_probe(struct platform_device *pdev)
TWL4030_INTERRUPTS_BCIIMR1A);
if (ret < 0) {
dev_err(&pdev->dev, "failed to unmask interrupts: %d\n", ret);
return ret;
goto fail;
}
reg = ~(u32)(TWL4030_VBATOV | TWL4030_VBUSOV | TWL4030_ACCHGOV);
@ -1102,6 +1111,10 @@ static int twl4030_bci_probe(struct platform_device *pdev)
twl4030_charger_enable_backup(0, 0);
return 0;
fail:
iio_channel_release(bci->channel_vac);
return ret;
}
static int __exit twl4030_bci_remove(struct platform_device *pdev)
@ -1112,6 +1125,8 @@ static int __exit twl4030_bci_remove(struct platform_device *pdev)
twl4030_charger_enable_usb(bci, false);
twl4030_charger_enable_backup(0, 0);
iio_channel_release(bci->channel_vac);
device_remove_file(&bci->usb->dev, &dev_attr_max_current);
device_remove_file(&bci->usb->dev, &dev_attr_mode);
device_remove_file(&bci->ac->dev, &dev_attr_max_current);

View file

@ -499,7 +499,8 @@ static int wm831x_power_probe(struct platform_device *pdev)
struct wm831x_power *power;
int ret, irq, i;
power = kzalloc(sizeof(struct wm831x_power), GFP_KERNEL);
power = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_power),
GFP_KERNEL);
if (power == NULL)
return -ENOMEM;
@ -536,7 +537,7 @@ static int wm831x_power_probe(struct platform_device *pdev)
NULL);
if (IS_ERR(power->wall)) {
ret = PTR_ERR(power->wall);
goto err_kmalloc;
goto err;
}
power->usb_desc.name = power->usb_name,
@ -572,7 +573,7 @@ static int wm831x_power_probe(struct platform_device *pdev)
irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "SYSLO"));
ret = request_threaded_irq(irq, NULL, wm831x_syslo_irq,
IRQF_TRIGGER_RISING, "System power low",
IRQF_TRIGGER_RISING | IRQF_ONESHOT, "System power low",
power);
if (ret != 0) {
dev_err(&pdev->dev, "Failed to request SYSLO IRQ %d: %d\n",
@ -582,7 +583,7 @@ static int wm831x_power_probe(struct platform_device *pdev)
irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "PWR SRC"));
ret = request_threaded_irq(irq, NULL, wm831x_pwr_src_irq,
IRQF_TRIGGER_RISING, "Power source",
IRQF_TRIGGER_RISING | IRQF_ONESHOT, "Power source",
power);
if (ret != 0) {
dev_err(&pdev->dev, "Failed to request PWR SRC IRQ %d: %d\n",
@ -595,7 +596,7 @@ static int wm831x_power_probe(struct platform_device *pdev)
platform_get_irq_byname(pdev,
wm831x_bat_irqs[i]));
ret = request_threaded_irq(irq, NULL, wm831x_bat_irq,
IRQF_TRIGGER_RISING,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
wm831x_bat_irqs[i],
power);
if (ret != 0) {
@ -626,8 +627,7 @@ err_usb:
power_supply_unregister(power->usb);
err_wall:
power_supply_unregister(power->wall);
err_kmalloc:
kfree(power);
err:
return ret;
}
@ -654,7 +654,6 @@ static int wm831x_power_remove(struct platform_device *pdev)
power_supply_unregister(wm831x_power->battery);
power_supply_unregister(wm831x_power->wall);
power_supply_unregister(wm831x_power->usb);
kfree(wm831x_power);
return 0;
}